-- music.lua:音乐播放模块(含问候语工具) local music = {} -- ========== 内部状态变量 ========== local media = nil local isPlayingMusic = false local readytoplay = false local preparing = false local isDownLoading = false local playingType = 1 -- 1列表循环 2随机 3单曲 4单次 local playingNum = 0 local lastNum = 0 local MusicList = {} local arrayList = ArrayList() local gedanList = ArrayList() local musresult = nil local searchingName = "" local shuaxin = nil local notTouching = true local moduleRunning = false -- 外部传入的资源与UI映射 local res = { lovepic = nil, unlovepic = nil, closepic = nil, seekbarId = nil, timeNowId = nil, timeEndId = nil, playBtnId = nil, loveBtnId = nil, title1Id = nil, title2Id = nil, loadingId = nil, } -- ========== 工具函数(模块私有) ========== local function safeGetView(id) if not id then return nil end local ok, view = pcall(function() return luajava.getIdView(id) end) if ok and view then return view end return nil end local function formatTime(value) local seconds = math.floor(value / 1000) local minutes = math.floor(seconds / 60) local remaining_seconds = seconds % 60 return string.format("%d:%02d", minutes, remaining_seconds) end -- ========== 公开工具:问候语 ========== function music.greeting() local hour = tonumber(os.date("%H")) if hour >= 5 and hour < 12 then return "宝宝上午好 ☁" elseif hour >= 12 and hour < 14 then return "宝宝中午好 ☀" elseif hour >= 14 and hour < 19 then return "宝宝下午好 ☕" elseif hour >= 19 and hour < 24 then return "宝宝晚上好 🌙" elseif hour >= 0 and hour < 3 then return "宝贝,夜深了,早点休息" else return "还在打游戏,快点睡觉觉,凌晨了,要注意身体" end end -- ========== 歌单管理 ========== local function saveMusicList() luajava.startThread(function() local tab = {} for i,v in pairs(MusicList) do if v[4]==true then table.insert(tab,v) end end local pat='/sdcard/Violet/.收藏歌单' file.write(pat,tostring(tab)) end) end local function haveList() if #MusicList<1 then luajava.startThread(function() gg.alert('您还没有收藏歌曲\n请先搜索想听的歌曲添加到播放列表\n点击左侧爱心可以收藏,每次重新启动时只会保留收藏的歌曲') end) return false else return true end end local function noMusic() luajava.startThread(function() if media then pcall(function() media:pause() end) end isPlayingMusic=false luajava.runUiThread(function() local skb = safeGetView(res.seekbarId) if skb then skb:setValue(0) end local playBtn = safeGetView(res.playBtnId) if playBtn then playBtn:setSpeed(-1) playBtn:playAnimation() end if skb then skb:setValueTo(1) end local loveBtn = safeGetView(res.loveBtnId) if loveBtn and res.unlovepic then loveBtn:setImageDrawable(res.unlovepic) loveBtn:setColorFilter(0xffffffff) end local title1 = safeGetView(res.title1Id) if title1 then title1:setText(music.greeting()) end -- 使用 music.greeting() local title2 = safeGetView(res.title2Id) if title2 then title2:setText('') end end) gg.alert('歌单已清空,请先搜索想听的歌曲') end) end -- ========== MediaPlayer 初始化(省略,保持原样) ========== local function initMediaPlayer() local MediaPlayer = luajava.bindClass("android.media.MediaPlayer") media = luajava.new(MediaPlayer) media:setOnPreparedListener(luajava.createProxy("android.media.MediaPlayer$OnPreparedListener", { onPrepared=function(mp) if not moduleRunning then return end mp:start() local r=media:getDuration() luajava.runUiThread(function() local skb = safeGetView(res.seekbarId) if skb then skb:setValueTo(r) end local timeNow = safeGetView(res.timeNowId) if timeNow then timeNow:setText('0:00') end local timeEnd = safeGetView(res.timeEndId) if timeEnd then timeEnd:setText(formatTime(r)) end local playBtn = safeGetView(res.playBtnId) if playBtn then playBtn:setSpeed(1) playBtn:playAnimation() end end) isPlayingMusic=true readytoplay=true preparing=false end })) media:setOnCompletionListener(luajava.createProxy("android.media.MediaPlayer$OnCompletionListener", { onCompletion=function() if not moduleRunning then return end if isPlayingMusic then if isDownLoading then return end isPlayingMusic=false pcall(function() media:pause() end) luajava.runUiThread(function() local skb = safeGetView(res.seekbarId) if skb then skb:setValue(0) end local playBtn = safeGetView(res.playBtnId) if playBtn then playBtn:setSpeed(-1) playBtn:playAnimation() end end) music.playNext() end end })) media:setOnErrorListener(luajava.createProxy("android.media.MediaPlayer$OnErrorListener", { onError=function() return true end })) end -- ========== 播放控制 ========== function music.play(m) if not moduleRunning or not media then return 0 end pcall(function() media:reset() end) if not readytoplay then luajava.post(function() local skb = safeGetView(res.seekbarId) if skb then skb:setVisibility(View.VISIBLE) end end) end if #MusicList==0 then noMusic() return 0 end if isDownLoading then return 0 end local txt='/sdcard/Violet/音乐缓存/'..m[1] luajava.startThread(function() if not moduleRunning then return end luajava.runUiThread(function() local skb = safeGetView(res.seekbarId) if skb then skb:setValue(0) end local title1 = safeGetView(res.title1Id) if title1 then title1:setText(m[2]) end local title2 = safeGetView(res.title2Id) if title2 then title2:setText(m[3]) end local loveBtn = safeGetView(res.loveBtnId) if loveBtn and res.lovepic and res.unlovepic then if m[4] then loveBtn:setImageDrawable(res.lovepic) loveBtn:setColorFilter(0xffEC2A37) else loveBtn:setImageDrawable(res.unlovepic) loveBtn:setColorFilter(0xffffffff) end end end) if file.length(txt,false)<500 then luajava.runUiThread(function() local loading = safeGetView(res.loadingId) if loading then loading:show() end end) isDownLoading=true file.download(string.format('http://music.163.com/song/media/outer/url?id=%s.mp3', m[1]),txt) isDownLoading=false end luajava.runUiThread(function() local loading = safeGetView(res.loadingId) if loading then loading:hide() end end) if preparing then return end local p,err=pcall(function() media:setDataSource(txt) end) if not moduleRunning then return end preparing=true pcall(function() media:prepare() end) end) end function music.playNext(notAuto) if not moduleRunning then return end if #MusicList==0 then noMusic() return 0 end lastNum=playingNum if playingType==1 then if playingNum>=#MusicList then playingNum=1 else playingNum=playingNum+1 end if music.play(MusicList[playingNum])==0 then playingNum=lastNum end elseif playingType==2 then local newnum=math.random(1,#MusicList) while true do if playingNum==newnum then newnum=math.random(1,#MusicList) else playingNum=newnum break end end if music.play(MusicList[playingNum])==0 then playingNum=lastNum end elseif playingType==3 then luajava.runUiThread(function() local skb = safeGetView(res.seekbarId) if skb then skb:setValue(0) end end) media:seekTo(0) isPlayingMusic=true if notAuto then if playingNum>=#MusicList then playingNum=1 else playingNum=playingNum+1 end if music.play(MusicList[playingNum])==0 then playingNum=lastNum end end elseif playingType==4 then luajava.runUiThread(function() local skb = safeGetView(res.seekbarId) if skb then skb:setValue(0) end end) isPlayingMusic=false media:seekTo(0) media:pause() if notAuto then if playingNum>=#MusicList then playingNum=1 else playingNum=playingNum+1 end if music.play(MusicList[playingNum])==0 then playingNum=lastNum end end end end function music.playLast() if not moduleRunning then return end if #MusicList==0 then noMusic() return 0 end if playingNum==1 then playingNum=#MusicList else playingNum=playingNum-1 end music.play(MusicList[playingNum]) end function music.togglePlay() if not moduleRunning then return end if not haveList() then return end if not readytoplay then music.play(MusicList[1]) return 0 end local playBtn = safeGetView(res.playBtnId) if isPlayingMusic then if playBtn then playBtn:setSpeed(-1) playBtn:playAnimation() end isPlayingMusic=false pcall(function() media:pause() end) else if playBtn then playBtn:setSpeed(1) playBtn:playAnimation() end isPlayingMusic=true pcall(function() media:start() end) end end function music.seekTo(pos) if moduleRunning and readytoplay and media then pcall(function() media:seekTo(pos) end) end end function music.switchPlayMode() if not moduleRunning then return 1 end if playingType==1 then playingType=2 return 2 elseif playingType==2 then playingType=3 pcall(function() media:setLooping(true) end) return 3 elseif playingType==3 then playingType=4 pcall(function() media:setLooping(false) end) return 4 elseif playingType==4 then playingType=1 return 1 end end function music.toggleCurrentFavorite() if not moduleRunning or not haveList() or not readytoplay then return end if playingNum~=0 then if MusicList[playingNum][4] then MusicList[playingNum][4]=false saveMusicList() return false else MusicList[playingNum][4]=true saveMusicList() return true end end end -- ========== 搜索相关 ========== function music.search(s, type, offset, total, limit, clear) if not moduleRunning then return false end searchingName=s if clear then arrayList:clear() end luajava.runUiThread(function() pcall(function() music.searchAdapter:notifyDataSetChanged() end) end) local args = { s = s, type = type or 1, offset = offset or 0, total = total or true, limit = limit or 20 } local ljson = require('ljson') local url = string.format('http://music.163.com/api/search/get?%s', http.arg2data(args)) local resp = gg.makeRequest(url) if not isTable(resp) then return false, tostring(resp) end local content = resp.content local jsont = ljson.decode(content) if jsont.code ~= 200 then return false, jsont.msg and tostring(jsont.msg) or content end musresult = jsont.result for i, v in ipairs(musresult.songs) do arrayList:add(v) end luajava.runUiThread(function() pcall(function() music.searchAdapter:notifyDataSetChanged() end) end) return true end -- ========== 适配器初始化 ========== local function initPlaylistAdapter() music.playlistAdapter = ui.BaseAdapter({ getCount = function() return gedanList:size() end, getItem = function(position) return gedanList:get(position) end, getItemId = function(position) return position end, getView = function(position, convertView1, parent) local info = music.playlistAdapter:getItem(position) if not convertView1 then convertView1 = luajava.loadlayout({ LinearLayout, layout_width='match_parent', gravity='center_vertical', padding='4dp', {ImageView, layout_width='24dp', layout_height='24dp'}, {TextView, layout_weight=1, textColor='#25253C', textSize='14sp', gravity='center', layout_width='match_parent'}, {ImageView, layout_width='24dp', layout_height='24dp', padding='-2dp', colorFilter=0xff4B484F} }) end local Iconv1=convertView1:getChildAt(0) local Textv1 = convertView1:getChildAt(1) local Iconv2=convertView1:getChildAt(2) Textv1:setText(info[2]) if info[4] then Iconv1:setImageDrawable(res.lovepic) Iconv1:setColorFilter(0xffEC2A37) else Iconv1:setImageDrawable(res.unlovepic) Iconv1:setColorFilter(0xff4B484F) end convertView1:setOnClickListener(function(v) music.play(MusicList[position+1]) end) Iconv1:setOnClickListener(function(v) luajava.startThread(function() local n=math.floor(position)+1 if MusicList[n][4] then MusicList[n][4]=false v:setImageDrawable(res.unlovepic) v:setColorFilter(0xff4B484F) if playingNum==n then local loveBtn = safeGetView(res.loveBtnId) if loveBtn then loveBtn:setImageDrawable(res.unlovepic) loveBtn:setColorFilter(0xffffffff) end end saveMusicList() else MusicList[n][4]=true v:setImageDrawable(res.lovepic) v:setColorFilter(0xffEC2A37) if playingNum==n then local loveBtn = safeGetView(res.loveBtnId) if loveBtn then loveBtn:setImageDrawable(res.lovepic) loveBtn:setColorFilter(0xffEC2A37) end end saveMusicList() end music.refreshPlaylist() end) end) Iconv2:setImageDrawable(res.closepic) Iconv2:setOnClickListener(function() luajava.startThread(function() local n=math.floor(position)+1 if gg.alert('确定删除['..MusicList[n][2]..']?','点错了','确定')==2 then table.remove(MusicList,n) if playingNum==n then if not isPlayingMusic then playingNum=playingNum-1 music.playLast() pcall(function() media:pause() end) else music.playLast() end end music.refreshPlaylist() if #MusicList==0 then noMusic() end end end) end) return convertView1 end }) end function music.refreshPlaylist() gedanList:clear() for i, v in ipairs(MusicList) do gedanList:add(v) end luajava.runUiThread(function() pcall(function() music.playlistAdapter:notifyDataSetChanged() end) end) end local function initSearchAdapter() music.searchAdapter = ui.BaseAdapter({ getCount = function() return arrayList:size() end, getItem = function(position) return arrayList:get(position) end, getItemId = function(position) return position end, getView = function(position, convertView, parent) local info = music.searchAdapter:getItem(position) if shuaxin ~= position and position + 1 == music.searchAdapter:getCount() and position + 1 < musresult.songCount then shuaxin = position luajava.startThread(function() music.search(searchingName, nil, math.int(position + 1)) end) end local Textv, Textv2 if not convertView then convertView = luajava.loadlayout({ LinearLayout, layout_width='match_parent', orientation='vertical', padding='4dp', }) Textv = luajava.loadlayout({TextView, textColor='#ffffff', textSize='14sp', gravity='center', layout_width='match_parent'}) Textv2 = luajava.loadlayout({TextView, textColor='#5D6981', textSize='11sp', gravity='center', layout_width='match_parent'}) convertView:addView(Textv) convertView:addView(Textv2) else Textv = convertView:getChildAt(0) Textv2 = convertView:getChildAt(1) end local a=info.artists local artname = "" if a[1]~=nil then artname=a[1].name end if a[2]~=nil then for i=2,#a do if a[i].name~=nil then artname=artname..'/'..a[i].name end end end Textv:setText(info.name) Textv2:setText(artname) return convertView end }) end function music.addAndPlay(position) if not moduleRunning then return end local item = music.searchAdapter:getItem(position) local a=item.artists local artname = "" if a[1]~=nil then artname=a[1].name end if a[2]~=nil then for i=2,#a do if a[i].name~=nil then artname=artname..'/'..a[i].name end end end local checkmus = true for i,v in pairs(MusicList) do if v[1]==item.id then playingNum=i checkmus=false end end if checkmus then playingNum=#MusicList+1 table.insert(MusicList,playingNum,{item.id,item.name,artname,false}) music.refreshPlaylist() end music.play(MusicList[playingNum]) if item.fee == 1 then gg.alert('VIP歌曲可能不支持播放或者只能试听30秒') end end -- ========== 进度轮询 ========== local function startProgressLoop() luajava.startThread(function() while moduleRunning do if moduleRunning and isPlayingMusic and media then local ok, nowm = pcall(function() return media:getCurrentPosition() end) if ok and nowm then if notTouching then luajava.runUiThread(function() if not moduleRunning then return end local skb = safeGetView(res.seekbarId) if skb and nowm < skb:getValueTo() then pcall(function() skb:setValue(nowm) end) end end) end luajava.runUiThread(function() if not moduleRunning then return end local timeView = safeGetView(res.timeNowId) if timeView then pcall(function() timeView:setText(formatTime(nowm)) end) end end) end end gg.sleep(500) end end) end function music.setTouching(state) notTouching = state end -- ========== 初始化和销毁 ========== function music.init(config) moduleRunning = true for k,v in pairs(config) do res[k] = v end initMediaPlayer() initPlaylistAdapter() initSearchAdapter() local pat='/sdcard/Violet/.收藏歌单' if file.exists(pat) then load('MusicList='..file.read(pat))() end if #MusicList>0 then playingNum=1 music.refreshPlaylist() end startProgressLoop() end function music.destroy() moduleRunning = false gg.sleep(100) if media then pcall(function() media:stop() media:release() end) media = nil end isPlayingMusic = false readytoplay = false preparing = false isDownLoading = false arrayList:clear() gedanList:clear() MusicList = {} end function music.isPlaying() return isPlayingMusic and moduleRunning end function music.getPlayingIndex() return playingNum end function music.getPlaylist() return MusicList end function music.getPlayMode() return playingType end function music.formatTime(ms) return formatTime(ms) end return music