-- music.lua - Music player functionality -- Handles playback, playlists, library, and now playing local music_doc = nil -- Player state local player_state = { is_playing = false, is_shuffled = false, repeat_mode = "off", -- off, all, one current_time = 0, duration = 234, -- 3:54 volume = 80 } -- Current track local current_track = { id = "1", title = "Midnight City", artist = "M83", album = "Hurry Up, We're Dreaming", duration = 234, art_color = "#667eea" } -- Playlists local playlists = { {id = "liked", name = "Liked Songs", count = 127, color = "#dc2626"}, {id = "daily1", name = "Daily Mix 1", count = 50, color = "#667eea"}, {id = "release", name = "Release Radar", count = 30, color = "#16a34a"}, {id = "chill", name = "Chill Vibes", count = 45, color = "#f093fb"}, {id = "workout", name = "Workout Mix", count = 35, color = "#2563eb"}, {id = "focus", name = "Focus Flow", count = 40, color = "#4facfe"} } -- Recently played local recently_played = { {id = "pop", name = "Pop Hits", type = "Playlist", color = "#43e97b"}, {id = "electronic", name = "Electronic", type = "Playlist", color = "#fa709a"}, {id = "jazz", name = "Jazz Classics", type = "Playlist", color = "#667eea"}, {id = "rock", name = "Rock Legends", type = "Playlist", color = "#f093fb"} } -- Song queue local queue = { {id = "1", title = "Midnight City", artist = "M83", duration = 234}, {id = "2", title = "Intro", artist = "The xx", duration = 128}, {id = "3", title = "Retrograde", artist = "James Blake", duration = 233}, {id = "4", title = "Tame Impala", artist = "The Less I Know The Better", duration = 218}, {id = "5", title = "Redbone", artist = "Childish Gambino", duration = 327} } local current_queue_index = 1 local timer_id = nil -- Initialize music app function initMusic(doc) print("[Music] Initializing...") music_doc = doc updateNowPlaying() updateMiniPlayer() renderPlaylists() renderRecentlyPlayed() end -- Format time (seconds to mm:ss) local function formatTime(seconds) local mins = math.floor(seconds / 60) local secs = seconds % 60 return string.format("%d:%02d", mins, secs) end -- Update now playing display function updateNowPlaying() if not music_doc then return end local title = music_doc:GetElementById("now-playing-title") local artist = music_doc:GetElementById("now-playing-artist") if title then title.inner_rml = current_track.title end if artist then artist.inner_rml = current_track.artist end end -- Update mini player function updateMiniPlayer() if not music_doc then return end local title = music_doc:GetElementById("mini-player-title") local artist = music_doc:GetElementById("mini-player-artist") local art = music_doc:GetElementById("mini-player-art") local play_btn = music_doc:GetElementById("mini-play-btn") if title then title.inner_rml = current_track.title end if artist then artist.inner_rml = current_track.artist end if art then art.style["background-color"] = current_track.art_color art.inner_rml = current_track.title:sub(1,1):upper() end if play_btn then local icon = player_state.is_playing and "pause.tga" or "play.tga" play_btn.inner_rml = [[]] end end -- Toggle play/pause function togglePlay() player_state.is_playing = not player_state.is_playing print("[Music] " .. (player_state.is_playing and "Playing" or "Paused")) if player_state.is_playing then startPlaybackTimer() else stopPlaybackTimer() end updateMiniPlayer() updatePlayButton() end -- Start playback timer function startPlaybackTimer() if setInterval then timer_id = setInterval(function() if player_state.is_playing then player_state.current_time = player_state.current_time + 1 if player_state.current_time >= current_track.duration then nextTrack() end updateProgress() end end, 1000) end end -- Stop playback timer function stopPlaybackTimer() if timer_id and clearInterval then clearInterval(timer_id) timer_id = nil end end -- Update play button function updatePlayButton() if not music_doc then return end local btn = music_doc:GetElementById("play-btn") if btn then local icon = player_state.is_playing and "pause.tga" or "play.tga" btn.inner_rml = [[]] end end -- Update progress display function updateProgress() if not music_doc then return end local current = music_doc:GetElementById("current-time") local total = music_doc:GetElementById("total-time") local progress = music_doc:GetElementById("progress-bar") if current then current.inner_rml = formatTime(player_state.current_time) end if total then total.inner_rml = formatTime(current_track.duration) end if progress then local percent = (player_state.current_time / current_track.duration) * 100 progress.style.width = percent .. "%" end end -- Next track function nextTrack() print("[Music] Next track") if player_state.is_shuffled then current_queue_index = math.random(1, #queue) else current_queue_index = current_queue_index + 1 if current_queue_index > #queue then if player_state.repeat_mode == "all" then current_queue_index = 1 else current_queue_index = #queue player_state.is_playing = false stopPlaybackTimer() end end end loadTrack(queue[current_queue_index]) end -- Previous track function previousTrack() print("[Music] Previous track") if player_state.current_time > 3 then -- Restart current track player_state.current_time = 0 else current_queue_index = current_queue_index - 1 if current_queue_index < 1 then current_queue_index = 1 end loadTrack(queue[current_queue_index]) end updateProgress() end -- Load a track function loadTrack(track) current_track.id = track.id current_track.title = track.title current_track.artist = track.artist current_track.duration = track.duration player_state.current_time = 0 updateNowPlaying() updateMiniPlayer() updateProgress() print("[Music] Now playing: " .. track.title .. " - " .. track.artist) if showToast then showToast("Now playing: " .. track.title) end end -- Toggle shuffle function toggleShuffle() player_state.is_shuffled = not player_state.is_shuffled print("[Music] Shuffle: " .. tostring(player_state.is_shuffled)) local btn = music_doc:GetElementById("shuffle-btn") if btn then if player_state.is_shuffled then btn:SetClass("active", true) else btn:SetClass("active", false) end end if showToast then showToast(player_state.is_shuffled and "Shuffle on" or "Shuffle off") end end -- Toggle repeat function toggleRepeat() if player_state.repeat_mode == "off" then player_state.repeat_mode = "all" elseif player_state.repeat_mode == "all" then player_state.repeat_mode = "one" else player_state.repeat_mode = "off" end print("[Music] Repeat: " .. player_state.repeat_mode) local btn = music_doc:GetElementById("repeat-btn") if btn then if player_state.repeat_mode ~= "off" then btn:SetClass("active", true) else btn:SetClass("active", false) end end if showToast then local msg = "Repeat: " .. player_state.repeat_mode showToast(msg) end end -- Toggle like function toggleLike() print("[Music] Toggle like") if showToast then showToast("Added to Liked Songs") end end -- Render playlists function renderPlaylists() if not music_doc then return end local container = music_doc:GetElementById("quick-access") if not container then return end local html = "" for i, pl in ipairs(playlists) do if i <= 6 then local initial = pl.name:sub(1,1):upper() html = html .. [[
]] .. initial .. [[
]] .. pl.name .. [[
]] end end container.inner_rml = html end -- Render recently played function renderRecentlyPlayed() if not music_doc then return end local container = music_doc:GetElementById("recent-row") if not container then return end local html = "" for _, item in ipairs(recently_played) do local initial = item.name:sub(1,1):upper() html = html .. [[
]] .. initial .. [[
]] .. item.name .. [[
]] .. item.type .. [[
]] end container.inner_rml = html end -- Open playlist function openPlaylist(playlist_id) print("[Music] Opening playlist: " .. playlist_id) if navigateTo then navigateTo("playlist_" .. playlist_id) else if showToast then showToast("Playlist: " .. playlist_id) end end end -- Open now playing function openNowPlaying() print("[Music] Opening now playing...") if navigateTo then navigateTo("now_playing") end end -- Open search function openSearch() print("[Music] Opening search...") if navigateTo then navigateTo("music_search") else if showToast then showToast("Search music") end end end -- Open library function openLibrary() print("[Music] Opening library...") if navigateTo then navigateTo("music_library") else if showToast then showToast("Your library") end end end -- Seek to position (0-1) function seekTo(position) player_state.current_time = math.floor(position * current_track.duration) updateProgress() end