### Vivimusic Basic Setup Script Source: https://github.com/vivizzz007/vivi-music/blob/main/development_guide.md This script clones the vivimusic repository, initializes submodules, generates protobuf files, builds the FFTW library for Android, creates a debug keystore if it doesn't exist, and finally assembles the universal Foss debug APK. Ensure you have the specified prerequisites installed. ```bash git clone https://github.com/vivimusicGroup/vivimusic cd vivimusic git submodule update --init --recursive cd app bash generate_proto.sh cd .. bash app/src/main/cpp/vibrafp/third_party/build-fftw-android.sh --ndk ~/Android/Sdk/ndk/27.0.12077973 --out app/src/main/cpp/vibrafp/third_party/fftw-android [ ! -f "app/persistent-debug.keystore" ] && keytool -genkeypair -v -keystore app/persistent-debug.keystore -storepass android -keypass android -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 -dname "CN=Android Debug,O=Android,C=US" || echo "Keystore already exists." ./gradlew :app:assembleuniversalFossDebug ls app/build/outputs/apk/universalFoss/debug/app-universal-foss-debug.apk ``` -------------------------------- ### Start YouTube Radio Queue Source: https://context7.com/vivizzz007/vivi-music/llms.txt Start a YouTube radio queue from a known song. Internally creates a `WatchEndpoint` and uses YouTube's personalization for recommendations. ```kotlin val queue = YouTubeQueue.radio(mediaMetadata) // Internally creates WatchEndpoint(videoId = song.id) and lets YouTube personalize recommendations ``` -------------------------------- ### Build FFTW for Android Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/cpp/vibrafp/README.md Use this script to download, compile, and install FFTW3 static libraries for Android ABIs. Ensure the ANDROID_NDK_HOME environment variable is set correctly. ```bash cd third_party ANDROID_NDK_HOME=/path/to/ndk ./build-fftw-android.sh ``` -------------------------------- ### Project and Options Configuration Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/cpp/vibrafp/lib/CMakeLists.txt Sets the minimum CMake version, project name, and enables LTO optimization. It also defines a cache variable for the FFTW3 installation path. ```cmake cmake_minimum_required(VERSION 3.22) project(vibra_fp LANGUAGES CXX) # ========== Options ========== option(ENABLE_LTO "Enable thin-LTO compile/link flags" ON) # Optional: user can pass -DFFTW3_PATH=/path/to/install-android-fftw # Expected layout if FFTW3_PATH is provided: # //include/ -> headers # //lib/libfftw3.a -> static lib if(NOT DEFINED FFTW3_PATH) set(FFTW3_PATH "" CACHE PATH "Path to FFTW install root (optional)") endif() message(STATUS "Project source dir: ${CMAKE_SOURCE_DIR}") ``` -------------------------------- ### Wrap Static List as Queue Source: https://context7.com/vivizzz007/vivi-music/llms.txt Wrap a static list of `MediaItem`s as a queue, suitable for offline playlists. Allows specifying a title, items, start index, and resume position. ```kotlin val listQueue = ListQueue( title = "My Playlist", items = mediaItemList, startIndex = 2, position = 30_000L // resume at 30s ) ``` -------------------------------- ### Get Initial Queue Status Source: https://context7.com/vivizzz007/vivi-music/llms.txt Get the initial status of a playback queue. This is a suspend function that returns the initial item list, current index, and playback start position. ```kotlin val status: Queue.Status = queue.getInitialStatus() println("Queue title: ${status.title}") println("Starting at index: ${status.mediaItemIndex}") println("Items: ${status.items.size}") ``` -------------------------------- ### Get Liked Songs Sorted by Name Source: https://context7.com/vivizzz007/vivi-music/llms.txt Get liked songs sorted by name ascending. The result is collected into a list, and its size is printed. ```kotlin database.likedSongs(SongSortType.NAME, descending = false) .collect { liked -> println("Liked count: ${liked.size}") } ``` -------------------------------- ### Initialize Po Token Minter Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/assets/po_token.html Initializes and stores a Po token minter function globally. Handles potential errors during minter retrieval and validates the returned minter is a function. Use this during application setup. ```javascript async function createPoTokenMinter() { let mintCallback; try { const minterResult = await getMinter(); if (minterResult && typeof minterResult.then === 'function') { mintCallback = await minterResult; console.log('` + '`' + 'createPoTokenMinter` Promise resolved, mintCallback type: ' + '`' + typeof mintCallback + '`' + '); } else { mintCallback = minterResult; } } catch (e) { console.log('` + '`' + 'createPoTokenMinter` getMinter EXCEPTION: ' + e.message); throw new Error('GMC:Failed - getMinter() threw: ' + e.message); } if (!mintCallback) { throw new Error('APF:Undefined - mintCallback is undefined'); } if (typeof mintCallback !== 'function') { throw new Error('APF:NotFunction - mintCallback is not a function, got: ' + typeof mintCallback); } // Store the minter globally for reuse poTokenMinter = mintCallback; console.log('` + '`' + 'createPoTokenMinter` SUCCESS - Minter created and stored globally (type: ' + '`' + typeof mintCallback + '`' + ')'); } ``` -------------------------------- ### Initialize BotGuard Client Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/assets/po_token.html Loads the BotGuard VM and program, then waits for the asynchronous callback to populate VM functions. Throws errors if the VM or program cannot be loaded. ```javascript var bgVmFunctions = null; var bgVm = null; var bgProgram = null; var poTokenMinter = null; // Minter callback - created ONCE during init, reused for all tokens function loadBotGuard(challengeData) { bgVm = window[challengeData.globalName]; bgProgram = challengeData.program; bgVmFunctions = null; if (!bgVm) throw new Error('[BotGuardClient]: VM not found in the global object'); if (!bgVm.a) throw new Error('[BotGuardClient]: Could not load program'); // Use explicit variable capture instead of 'this' to avoid binding issues var vmFunctionsCallback = function ( asyncSnapshotFunction, shutdownFunction, passEventFunction, checkCameraFunction ) { bgVmFunctions = { asyncSnapshotFunction: asyncSnapshotFunction, shutdownFunction: shutdownFunction, passEventFunction: passEventFunction, checkCameraFunction: checkCameraFunction }; }; // Execute the BotGuard program try { bgVm.a(bgProgram, vmFunctionsCallback, true, undefined, function () {/** no-op */}, [ [], [] ]); } catch (e) { throw new Error('[BotGuardClient]: Failed to execute program: ' + e.message); } // Wait for vmFunctions to be populated (async callback) return new Promise(function (resolve, reject) { var attempts = 0; var maxAttempts = 10000; // 10 seconds at 1ms intervals var checkInterval = setInterval(function () { if (bgVmFunctions && bgVmFunctions.asyncSnapshotFunction) { clearInterval(checkInterval); resolve({ vmFunctions: bgVmFunctions, vm: bgVm, program: bgProgram }); } else if (attempts >= maxAttempts) { clearInterval(checkInterval); reject(new Error('[BotGuardClient]: Timeout waiting for asyncSnapshotFunction')); } attempts++; }, 1); }); } ``` -------------------------------- ### Build vivimusic App Source: https://github.com/vivizzz007/vivi-music/blob/main/AGENTS.md Use this command to build the app and check for compilation errors after making code changes. Ensure you are in the root directory of the project. ```bash ./gradlew :app:assembleuniversalFossDebug ``` -------------------------------- ### Run BotGuard and Snapshot Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/assets/po_token.html Executes the BotGuard interpreter JavaScript, loads the BotGuard VM, and performs a snapshot. Returns the webPoSignalOutput and the BotGuard response. ```javascript function runBotGuard(challengeData) { var interpreterJavascript = challengeData.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue; if (interpreterJavascript) { new Function(interpreterJavascript)(); } else { throw new Error('[BotGuardClient]: Could not load VM - no interpreter JavaScript'); } var webPoSignalOutput = []; return loadBotGuard({ globalName: challengeData.globalName, program: challengeData.program }) .then(function (botguard) { return snapshot(botguard, { webPoSignalOutput: webPoSignalOutput }); }) .then(function (botguardResponse) { return { webPoSignalOutput: webPoSignalOutput, botguardResponse: botguardResponse }; }); } ``` -------------------------------- ### Manage Offline Downloads with DownloadUtil Source: https://context7.com/vivizzz007/vivi-music/llms.txt Observe download states via DownloadUtil.downloads and trigger/remove downloads using the ExoPlayer DownloadManager. Download states are defined by Download.STATE constants. ```kotlin // Inject via Hilt @Inject lateinit var downloadUtil: DownloadUtil // Observe all active downloads downloadUtil.downloads.collect { downloads: Map -> downloads.forEach { (mediaId, dl) -> val pct = (dl.bytesDownloaded * 100f / dl.contentLength).coerceIn(0f, 100f) println("$mediaId: ${"%.1f".format(pct)}% state=${dl.state}") } } // Check Download.state constants: // Download.STATE_QUEUED = 0 // Download.STATE_DOWNLOADING = 1 // Download.STATE_COMPLETED = 3 // Download.STATE_FAILED = 4 // Download.STATE_REMOVING = 5 // Trigger download for a song (via DownloadManager in MusicService) val request = DownloadRequest.Builder(mediaId, mediaUri).build() downloadManager.addDownload(request) // Remove a downloaded song downloadManager.removeDownload(mediaId) ``` -------------------------------- ### FFTW3 FetchContent and Build Configuration Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/cpp/vibrafp/lib/CMakeLists.txt If FFTW3 is not found locally, this block uses FetchContent to download and build it from a tarball. It configures FFTW for a minimal static build, disabling shared libraries and tests, while enabling threads. ```cmake if(NOT FFTW3_STATIC_LIB OR NOT EXISTS "${FFTW3_STATIC_LIB}") message(STATUS "FFTW3 not found locally. Fetching and building from source...") include(FetchContent) # Use the locally downloaded tarball if available set(FFTW_LOCAL_TARBALL "${CMAKE_SOURCE_DIR}/../third_party/fftw-3.3.10.tar.gz") FetchContent_Declare( fftw3 URL "${FFTW_LOCAL_TARBALL}" URL_HASH MD5=8ccbf6a5ea78a16dbc3e1306e234cc5c ) # Configure FFTW options for a minimal static build set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) set(BUILD_TESTS OFF CACHE BOOL "" FORCE) set(ENABLE_THREADS ON CACHE BOOL "" FORCE) set(ENABLE_OPENMP OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(fftw3) endif() ``` -------------------------------- ### Build MediaMetadata Manually and Convert to/from Room Entity Source: https://context7.com/vivizzz007/vivi-music/llms.txt Demonstrates how to manually construct a MediaMetadata object and convert between MediaMetadata and Room's SongEntity. Useful for creating or updating song data. ```kotlin // Build a MediaMetadata manually val metadata = MediaMetadata( id = "dQw4w9WgXcQ", title = "Never Gonna Give You Up", artists = listOf(MediaMetadata.Artist(id = "UCuAXFkgsw1L7xaCfnd5JJOw", name = "Rick Astley")), duration = 213, thumbnailUrl = "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", album = MediaMetadata.Album(id = "MPREb_XVq4n0y00tQ", title = "Whenever You Need Somebody"), explicit = false, liked = false ) // Convert a Room Song entity back to MediaMetadata val song: Song = database.getSongById("dQw4w9WgXcQ") val mediaMetadata: MediaMetadata = song.toMediaMetadata() // mediaMetadata.id == "dQw4w9WgXcQ" // mediaMetadata.isVideoSong == false (ATV type → audio-only) // Persist back to the database val entity: SongEntity = mediaMetadata.toSongEntity() database.insert(entity) ``` -------------------------------- ### Load Next Page of Queue Items Source: https://context7.com/vivizzz007/vivi-music/llms.txt Load the next page of recommendations for a queue if available. Returns a list of `MediaItem`s. ```kotlin if (queue.hasNextPage()) { val moreItems: List = queue.nextPage() println("Loaded ${moreItems.size} more items") } ``` -------------------------------- ### Manage Song Like and Library State with RoomEntity Source: https://context7.com/vivizzz007/vivi-music/llms.txt Shows how to toggle the 'liked' and 'inLibrary' states for a SongEntity, including options for syncing these changes to YouTube Music. Use for updating user preferences. ```kotlin // Toggle like locally (no YouTube sync) val updatedSong = song.localToggleLike() // updatedSong.liked == true, updatedSong.likedDate == LocalDateTime.now() // Toggle like AND sync to YouTube Music val syncedSong = song.toggleLike() // Launches a Dispatchers.IO coroutine calling YouTube.likeVideo(id, newLikedState) // Toggle library membership (with YouTube sync) val librarySong = song.toggleLibrary(syncToYouTube = true) // If inLibrary was null → sets inLibrary = LocalDateTime.now() // Calls YouTube.toggleSongLibrary(id, addToLibrary=true) // Toggle library without network sync (e.g., local-only files) val localSong = song.toggleLibrary(syncToYouTube = false) ``` -------------------------------- ### Observe Queue Windows Source: https://context7.com/vivizzz007/vivi-music/llms.txt Observe the current queue windows. Collects a list of `Timeline.Window` objects and prints the number of items in the queue. ```kotlin playerConnection.queueWindows .collect { windows: List -> println("Queue has ${windows.size} items") } ``` -------------------------------- ### Queue / YouTubeQueue - Playback Queue Abstraction Source: https://context7.com/vivizzz007/vivi-music/llms.txt Interface for playback sources, including YouTube-sourced queues and static lists. Provides methods to initialize, load next pages, and filter queue items. ```APIDOC ## Queue / YouTubeQueue — Playback queue abstraction `Queue` is the interface for all playback sources. `YouTubeQueue` implements it for YouTube-sourced queues using the Innertube `YouTube.next()` API with automatic retry and radio fallback. `ListQueue` wraps a static list of `MediaItem`s. `YouTubeAlbumRadio` and `YouTubePlaylistQueue` cover album and playlist contexts. The `Queue.Status` result carries the initial item list, currently playing index, and playback start position. ### Start a YouTube radio from a known song ```kotlin val queue = YouTubeQueue.radio(mediaMetadata) // Internally creates WatchEndpoint(videoId = song.id) and lets YouTube personalize recommendations ``` ### Get initial queue state (suspend) ```kotlin val status: Queue.Status = queue.getInitialStatus() println("Queue title: ${status.title}") println("Starting at index: ${status.mediaItemIndex}") println("Items: ${status.items.size}") ``` ### Load next page of recommendations ```kotlin if (queue.hasNextPage()) { val moreItems: List = queue.nextPage() println("Loaded ${moreItems.size} more items") } ``` ### Filter explicit tracks before loading into player ```kotlin val cleanStatus = status.filterExplicit(enabled = true) ``` ### Filter video songs (keep audio-only tracks) ```kotlin val audioStatus = status.filterVideoSongs(disableVideos = true) ``` ### Wrap a static list as a queue (e.g. offline playlist) ```kotlin val listQueue = ListQueue( title = "My Playlist", items = mediaItemList, startIndex = 2, position = 30_000L // resume at 30s ) ``` ``` -------------------------------- ### Perform BotGuard Snapshot Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/assets/po_token.html Takes a snapshot asynchronously using the loaded BotGuard VM functions. Requires the botguard object with populated vmFunctions. ```javascript function snapshot(botguard, args) { return new Promise(function (resolve, reject) { if (!botguard.vmFunctions || !botguard.vmFunctions.asyncSnapshotFunction) { return reject(new Error('[BotGuardClient]: Async snapshot function not found')); } try { botguard.vmFunctions.asyncSnapshotFunction( function (response) { resolve(response); }, [ args.contentBinding, args.signedTimestamp, args.webPoSignalOutput, args.skipPrivacyBuffer ] ); } catch (e) { reject(new Error('[BotGuardClient]: Snapshot failed: ' + e.message)); } }); } ``` -------------------------------- ### Apply Parametric EQ with CustomEqualizerAudioProcessor Source: https://context7.com/vivizzz007/vivi-music/llms.txt Define an EQ profile using ParametricEQ and ParametricEQBand, then apply it to the CustomEqualizerAudioProcessor. Profiles are persisted via EQProfileRepository. ```kotlin // Define an EQ profile manually (AutoEQ format) val eq = ParametricEQ( preamp = -6.0, // dB preamp to prevent clipping bands = listOf( ParametricEQBand(frequency = 32.0, gain = 3.5, q = 0.7, filterType = FilterType.PK), ParametricEQBand(frequency = 125.0, gain = -2.0, q = 1.4, filterType = FilterType.PK), ParametricEQBand(frequency = 1000.0, gain = 1.0, q = 1.0, filterType = FilterType.PK), ParametricEQBand(frequency = 8000.0, gain = -3.0, q = 2.0, filterType = FilterType.PK), ParametricEQBand(frequency = 16000.0, gain = 4.0, q = 0.5, filterType = FilterType.HS) ) ) // Apply to the custom audio processor (injected or obtained from EqualizerService) equalizerAudioProcessor.applyProfile(eq) // Processor converts preamp dB → linear gain and creates biquad filters per band // Disable equalizer equalizerAudioProcessor.disable() // Save a named profile via EQProfileRepository val profile = SavedEQProfile( id = UUID.randomUUID().toString(), name = "Sony WH-1000XM4", deviceModel = "Sony WH-1000XM4", bands = eq.bands, preamp = eq.preamp, isCustom = true, isActive = false ) eqProfileRepository.saveProfile(profile) // Observe active profile reactively eqProfileRepository.activeProfile .collect { active -> println("Active EQ: ${active?.name ?: "None"}") } ``` -------------------------------- ### DataStore Preference Keys Source: https://context7.com/vivizzz007/vivi-music/llms.txt Configure user settings using DataStore Preferences keys for themes, playback behavior, lyrics providers, network options, Discord RPC, Last.fm scrobbling, and UI density. Read preferences in Composables/ViewModels and write them using context.dataStore.edit. ```kotlin // Read a preference in a Composable or ViewModel val darkMode by rememberPreference(DarkModeKey, defaultValue = DarkMode.AUTO.name) val dynamicTheme by rememberPreference(DynamicThemeKey, defaultValue = true) // Write a preference context.dataStore.edit { prefs -> prefs[SkipSilenceKey] = true prefs[AudioQualityKey] = AudioQuality.HIGH.name prefs[CrossfadeEnabledKey] = true prefs[CrossfadeDurationKey] = 5 // seconds prefs[EnableDiscordRPCKey] = true prefs[DiscordTokenKey] = "YOUR_DISCORD_TOKEN" prefs[EnableLastFMScrobblingKey] = true prefs[IpVersionKey] = IpVersion.IPV4.name prefs[PreferredLyricsProviderKey] = PreferredLyricsProvider.LRCLIB.name } ``` ```kotlin // DensityScale for UI compactness context.dataStore.edit { prefs -> prefs[DensityScaleKey] = DensityScale.COMPACT.value // 0.75f } ``` ```kotlin // SliderStyle for the player seek bar context.dataStore.edit { prefs -> prefs[SliderStyleKey] = SliderStyle.WAVY.name } ``` -------------------------------- ### Fetch Lyrics with LyricsHelper Source: https://context7.com/vivizzz007/vivi-music/llms.txt Inject LyricsHelper and use it to fetch lyrics for the current track. Results are cached and the provider order can be customized. Use getAllLyrics to stream all available lyrics concurrently. ```kotlin // Inject via Hilt and fetch best lyrics for current track @Inject lateinit var lyricsHelper: LyricsHelper // Get the best lyrics from preferred provider chain val result: LyricsWithProvider = lyricsHelper.getLyrics(mediaMetadata) if (result.lyrics != LyricsEntity.LYRICS_NOT_FOUND) { println("Lyrics from: ${result.provider}") println(result.lyrics) // LRC or plain text } else { println("No lyrics found for this track") } // Stream all available lyrics from every provider lyricsHelper.getAllLyrics( mediaId = "dQw4w9WgXcQ", songTitle = "Never Gonna Give You Up", songArtists = "Rick Astley", duration = 213, album = "Whenever You Need Somebody" ) { lyricsResult: LyricsResult -> println("[${lyricsResult.providerName}] ${lyricsResult.lyrics.take(80)}") } // Cancel in-flight lyrics request (e.g., track changed) lyricsHelper.cancelCurrentLyricsJob() // Change preferred provider via DataStore (LRCLIB, KUGOU, BETTER_LYRICS, SIMPMUSIC, YOULYPLUS) context.dataStore.edit { prefs[PreferredLyricsProviderKey] = PreferredLyricsProvider.LRCLIB.name } ``` -------------------------------- ### C++ Standard and Android System Libraries Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/cpp/vibrafp/lib/CMakeLists.txt Sets the C++ standard to C++11 for the vibra_fp target and links to system libraries 'log' and 'm' if building for Android. ```cmake # ========== C++ standard ========== set_target_properties(vibra_fp PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED YES) # ========== Android system libs ========== if (ANDROID) target_link_libraries(vibra_fp PRIVATE log m) endif() ``` -------------------------------- ### FFTW3 Target Linking Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/cpp/vibrafp/lib/CMakeLists.txt This section either creates an IMPORTED static library target for FFTW3 if found, or links to the FFTW3 target built via FetchContent. It handles the case where FFTW3 is not found, resulting in a fatal error. ```cmake if(FFTW3_STATIC_LIB AND EXISTS "${FFTW3_STATIC_LIB}") add_library(fftw3_static STATIC IMPORTED GLOBAL) set_target_properties(fftw3_static PROPERTIES IMPORTED_LOCATION "${FFTW3_STATIC_LIB}" INTERFACE_INCLUDE_DIRECTORIES "${FFTW3_INCLUDE_DIR}" ) message(STATUS "Using FFTW (imported static): ${FFTW3_STATIC_LIB}") target_link_libraries(vibra_fp PRIVATE fftw3_static) elseif(TARGET fftw3) message(STATUS "Using FFTW (built from source)") # When building from source using FetchContent, we need to point to the header location target_include_directories(vibra_fp PRIVATE ${fftw3_SOURCE_DIR}/api) target_link_libraries(vibra_fp PRIVATE fftw3) else() message(FATAL_ERROR "FFTW3 static library not found and failed to build from source.") endif() ``` -------------------------------- ### FFTW3 Path Detection and Configuration Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/cpp/vibrafp/lib/CMakeLists.txt Attempts to locate the FFTW3 static library and include directories, prioritizing per-ABI layouts for Android builds. It falls back to a repository-local layout if the primary path is not found. ```cmake set(FFTW3_INCLUDE_DIR "") set(FFTW3_STATIC_LIB "") if(FFTW3_PATH) # fallback layout set(FFTW3_STATIC_LIB "${FFTW3_PATH}/lib/libfftw3.a") set(FFTW3_INCLUDE_DIR "${FFTW3_PATH}/include") # prefer per-ABI layout if building for Android and file exists if(ANDROID AND ANDROID_ABI) set(CANDIDATE_LIB "${FFTW3_PATH}/${ANDROID_ABI}/lib/libfftw3.a") if(EXISTS "${CANDIDATE_LIB}") set(FFTW3_STATIC_LIB "${CANDIDATE_LIB}") set(FFTW3_INCLUDE_DIR "${FFTW3_PATH}/${ANDROID_ABI}/include") endif() endif() endif() # If FFTW3_PATH not provided, try the repository-local default layout: # /third_party/fftw-android//lib/libfftw3.a if(ANDROID AND (NOT FFTW3_STATIC_LIB OR NOT EXISTS "${FFTW3_STATIC_LIB}")) set(LOCAL_THIRD "${CMAKE_SOURCE_DIR}/../third_party/fftw-android") if(DEFINED ANDROID_ABI) set(LOCAL_LIB "${LOCAL_THIRD}/${ANDROID_ABI}/lib/libfftw3.a") if(EXISTS "${LOCAL_LIB}") set(FFTW3_STATIC_LIB "${LOCAL_LIB}") set(FFTW3_INCLUDE_DIR "${LOCAL_THIRD}/${ANDROID_ABI}/include") endif() endif() endif() ``` -------------------------------- ### Observe Library Songs Sorted by Play Time Source: https://context7.com/vivizzz007/vivi-music/llms.txt Observe all library songs, sorted by play time descending. Requires importing the `Flow` and `collect` extensions. ```kotlin database.songs(SongSortType.PLAY_TIME, descending = true) .collect { songs: List -> songs.forEach { println("${it.song.title} — ${it.song.totalPlayTime}ms") } } ``` -------------------------------- ### Configure LTO and Symbol Stripping for Release Builds Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/cpp/vibrafp/lib/CMakeLists.txt Applies thin-LTO, section garbage collection, and symbol stripping to the vibra_fp target for release configurations. Enable LTO using the ENABLE_LTO CMake variable. ```cmake if(ENABLE_LTO) target_compile_options(vibra_fp PRIVATE $<$:-flto=thin> ) target_link_options(vibra_fp PRIVATE $<$:-flto=thin> $<$:-Wl,--gc-sections> $<$:-Wl,--strip-all> ) else() target_link_options(vibra_fp PRIVATE $<$:-Wl,--gc-sections> $<$:-Wl,--strip-all> ) endif() ``` -------------------------------- ### Compiler and Linker Options Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/cpp/vibrafp/lib/CMakeLists.txt Configures compiler and linker options for the vibra_fp target, including optimization levels, debug information, section stripping, visibility, frame pointer omission, and position-independent code (PIC). Options are differentiated between Release and non-Release configurations. ```cmake # ========== Compiler / Linker options ========== # Common options (Release vs Debug) target_compile_options(vibra_fp PRIVATE $<$:-O2> $<$>:-O0> $<$>:-g> $<$:-ffunction-sections> $<$:-fdata-sections> $<$:-fvisibility=hidden> $<$:-fomit-frame-pointer> $<$:-fPIC> ) ``` -------------------------------- ### DatabaseDao - Room DAO Queries Source: https://context7.com/vivizzz007/vivi-music/llms.txt Provides reactive Flow-based queries for songs, albums, artists, playlists, and play history. Supports sorting by various criteria. ```APIDOC ## DatabaseDao - Room DAO Queries `DatabaseDao` is the central Room `@Dao` interface that provides reactive `Flow`-based queries for songs, albums, artists, playlists, and play history. The `songs()`, `likedSongs()`, and `artistSongs()` convenience functions accept `SortType` enums and a `descending: Boolean` to produce locale-aware sorted streams. ### Observe all library songs, sorted by play time descending ```kotlin database.songs(SongSortType.PLAY_TIME, descending = true) .collect { songs: List -> songs.forEach { println("${it.song.title} — ${it.song.totalPlayTime}ms") } } ``` ### Get liked songs sorted by name ascending ```kotlin database.likedSongs(SongSortType.NAME, descending = false) .collect { liked -> println("Liked count: ${liked.size}") } ``` ### Fetch songs for a specific artist sorted by creation date ```kotlin database.artistSongs( artistId = "UCuAXFkgsw1L7xaCfnd5JJOw", sortType = ArtistSongSortType.CREATE_DATE, descending = true, limit = 50 ).collect { artistSongs -> /* render list */ } ``` ### Observe songs in a playlist in position order ```kotlin database.playlistSongs(playlistId = "PLlocal_xyz") .collect { songs: List -> songs.forEachIndexed { i, ps -> println("$i: ${ps.song.title}") } } ``` ### Observe liked song count reactively ```kotlin database.likedSongsCount() .collect { count -> println("You have liked $count songs") } ``` ``` -------------------------------- ### Observe Shuffle and Repeat Mode Source: https://context7.com/vivizzz007/vivi-music/llms.txt Observe the shuffle and repeat modes of the player. Collects boolean for shuffle and an integer for repeat mode (OFF, ONE, ALL). ```kotlin playerConnection.shuffleModeEnabled.collect { enabled -> /* update UI */ } playerConnection.repeatMode.collect { mode -> /* REPEAT_MODE_OFF / ONE / ALL */ } ``` -------------------------------- ### Fetch Artist Songs with Sorting and Limit Source: https://context7.com/vivizzz007/vivi-music/llms.txt Fetch songs for a specific artist, sorted by creation date in descending order, with a limit of 50 items. Requires `artistId`, `sortType`, and `descending` parameters. ```kotlin database.artistSongs( artistId = "UCuAXFkgsw1L7xaCfnd5JJOw", sortType = ArtistSongSortType.CREATE_DATE, descending = true, limit = 50 ).collect { artistSongs -> /* render list */ } ``` -------------------------------- ### Observe Playlist Songs by Position Source: https://context7.com/vivizzz007/vivi-music/llms.txt Observe songs in a playlist in their defined position order. Requires a `playlistId`. ```kotlin database.playlistSongs(playlistId = "PLlocal_xyz") .collect { songs: List -> songs.forEachIndexed { i, ps -> println("$i: ${ps.song.title}") } } ``` -------------------------------- ### Create poToken Minter Callback Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/assets/po_token.html Creates the poToken minter callback by calling the minter factory function from webPoSignalOutput with the integrity token. This function must be called exactly once during initialization. It handles both synchronous and asynchronous return values from the minter factory. ```javascript async function createPoTokenMinter(webPoSignalOutput, integrityToken) { console.log('[createPoTokenMinter] ENTER - webPoSignalOutput type: ' + typeof webPoSignalOutput + ', length: ' + (webPoSignalOutput ? webPoSignalOutput.length : 'null')); console.log('[createPoTokenMinter] integrityToken type: ' + typeof integrityToken + ', is array: ' + Array.isArray(integrityToken)); // Get the minter factory function from webPoSignalOutput var getMinter = webPoSignalOutput[0]; console.log('[createPoTokenMinter] getMinter type: ' + typeof getMinter); if (!getMinter) { throw new Error('PMD:Undefined - webPoSignalOutput[0] is not defined'); } if (typeof getMinter !== 'function') { throw new Error('PMD:NotFunction - webPoSignalOutput[0] is not a function, got: ' + typeof getMinter); } // Create the mint callback by passing the integrity token - THIS MUST ONLY HAPPEN ONCE! // NOTE: getMinter() may return a Promise, so we await it console.log('[createPoTokenMinter] Calling getMinter(integrityToken)...'); var mintCallback; try { var minterResult = getMinter(integrityToken); console.log('[createPoTokenMinter] getMinter returned type: ' + typeof minterResult + ', isPromise: ' + (minterResult && typeof minterResult.then === 'function')); // Handle both sync and async getMinter if (minterResult && typeof minterResult.then === 'function') { console.log('[createPoTokenMinter] getMinter returned a Promise, awaiting...') ``` -------------------------------- ### ListenTogetherManager Usage Source: https://context7.com/vivizzz007/vivi-music/llms.txt Inject ListenTogetherManager via Hilt and observe connection state, room role, join requests, buffering users, and chat messages. Use playerConnection.shouldBlockPlaybackChanges to control UI based on room role. ```kotlin // Inject via Hilt @Inject lateinit var listenTogetherManager: ListenTogetherManager // Observe connection state listenTogetherManager.connectionState .collect { state -> println("Connection: $state") } // Observe room role (HOST / GUEST / NONE) listenTogetherManager.role .collect { role -> println("Role: $role") } // Observe pending join requests (host side) listenTogetherManager.pendingJoinRequests .collect { requests -> requests.forEach { req -> println("Join request from ${req.username}") } } // Observe buffering users (host side) listenTogetherManager.bufferingUsers .collect { users -> println("Buffering: ${users.joinToString()}") } // Observe chat messages listenTogetherManager.chatMessages .collect { messages -> messages.forEach { m -> println("${m.username}: ${m.message}") } } // Expose sync state for UI blocking (guest cannot seek/skip) playerConnection.shouldBlockPlaybackChanges = { listenTogetherManager.role.value == RoomRole.GUEST } ``` -------------------------------- ### Target Include Directories Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/cpp/vibrafp/lib/CMakeLists.txt Specifies the include directories for the vibra_fp target, making headers accessible during compilation. ```cmake target_include_directories(vibra_fp PRIVATE ${CMAKE_SOURCE_DIR}/../include ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/algorithm ${CMAKE_SOURCE_DIR}/audio ${CMAKE_SOURCE_DIR}/utils ) ``` -------------------------------- ### PlayerConnection - Playback State Observation Source: https://context7.com/vivizzz007/vivi-music/llms.txt Binds to MusicService and exposes real-time playback state as Kotlin StateFlows. Provides safe access to ExoPlayer and reactive flows for metadata, lyrics, queue, shuffle/repeat, and errors. ```APIDOC ## PlayerConnection — Playback state observation `PlayerConnection` binds to `MusicService` via the `MusicBinder` and exposes all real-time playback state as Kotlin `StateFlow`s. It provides safe access to the underlying `ExoPlayer` instance with initialization guards, and exposes reactive flows for current metadata, lyrics, queue windows, shuffle/repeat mode, and playback errors. ### Observe whether music is playing (accounts for Cast sessions too) ```kotlin playerConnection.isEffectivelyPlaying .collect { playing -> updatePlayPauseButton(playing) } ``` ### Observe current track metadata ```kotlin playerConnection.mediaMetadata .collect { metadata: MediaMetadata? -> metadata?.let { showNowPlaying(it.title, it.artists.first().name) } } ``` ### Observe current synced lyrics from DB ```kotlin playerConnection.currentLyrics .collect { lyrics: LyricsEntity? -> displayLyrics(lyrics?.lyrics) } ``` ### Observe playback errors ```kotlin playerConnection.error .collect { ex: PlaybackException? -> ex?.let { showError("Playback error: ${it.errorCodeName}") } } ``` ### Observe queue windows ```kotlin playerConnection.queueWindows .collect { windows: List -> println("Queue has ${windows.size} items") } ``` ### Check shuffle and repeat state ```kotlin playerConnection.shuffleModeEnabled.collect { enabled -> /* update UI */ } playerConnection.repeatMode.collect { mode -> /* REPEAT_MODE_OFF / ONE / ALL */ } ``` ### Mute state ```kotlin playerConnection.isMuted.collect { muted -> updateMuteIcon(muted) } ``` ``` -------------------------------- ### Observe Liked Song Count Reactively Source: https://context7.com/vivizzz007/vivi-music/llms.txt Observe the count of liked songs reactively. This flow emits the current count whenever it changes. ```kotlin database.likedSongsCount() .collect { count -> println("You have liked $count songs") } ``` -------------------------------- ### Observe Music Playback State Source: https://context7.com/vivizzz007/vivi-music/llms.txt Observe whether music is effectively playing, accounting for Cast sessions. Collects the boolean state and updates the UI accordingly. ```kotlin playerConnection.isEffectivelyPlaying .collect { playing -> updatePlayPauseButton(playing) } ``` -------------------------------- ### Observe Synced Lyrics Source: https://context7.com/vivizzz007/vivi-music/llms.txt Observe current synced lyrics from the database. Collects `LyricsEntity` and displays the lyrics content. ```kotlin playerConnection.currentLyrics .collect { lyrics: LyricsEntity? -> displayLyrics(lyrics?.lyrics) } ``` -------------------------------- ### Observe Playback Errors Source: https://context7.com/vivizzz007/vivi-music/llms.txt Observe playback errors. Collects `PlaybackException` and displays an error message based on the error code. ```kotlin playerConnection.error .collect { ex: PlaybackException? -> ex?.let { showError("Playback error: ${it.errorCodeName}") } } ``` -------------------------------- ### Library Sources Definition Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/cpp/vibrafp/lib/CMakeLists.txt Defines the list of source files that will be compiled into the vibra_fp shared library. ```cmake set(LIBVIBRA_SOURCES vibra.cpp vibra_jni.cpp algorithm/signature.cpp algorithm/frequency.cpp algorithm/signature_generator.cpp audio/wav.cpp audio/downsampler.cpp ) add_library(vibra_fp SHARED ${LIBVIBRA_SOURCES}) ``` -------------------------------- ### Observe Current Track Metadata Source: https://context7.com/vivizzz007/vivi-music/llms.txt Observe the current track's metadata, including title and artists. Collects `MediaMetadata` and displays the now playing information. ```kotlin playerConnection.mediaMetadata .collect { metadata: MediaMetadata? -> metadata?.let { showNowPlaying(it.title, it.artists.first().name) } } ``` -------------------------------- ### Apply Version Script for Exported Symbols Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/cpp/vibrafp/lib/CMakeLists.txt Links the vibra_fp target with a version script if specified by VIBRA_EXPORT_SCRIPT. This controls which symbols are exported from the library. ```cmake set(VIBRA_EXPORT_SCRIPT "${CMAKE_SOURCE_DIR}/vibra.sym") if(EXISTS "${VIBRA_EXPORT_SCRIPT}") message(STATUS "Using version script: ${VIBRA_EXPORT_SCRIPT}") target_link_options(vibra_fp PRIVATE "-Wl,--version-script=${VIBRA_EXPORT_SCRIPT}") endif() ``` -------------------------------- ### Observe Mute State Source: https://context7.com/vivizzz007/vivi-music/llms.txt Observe the mute state of the player. Collects a boolean value indicating whether the player is muted and updates the mute icon. ```kotlin playerConnection.isMuted.collect { muted -> updateMuteIcon(muted) } ``` -------------------------------- ### Obtain Po Token Source: https://github.com/vivizzz007/vivi-music/blob/main/app/src/main/assets/po_token.html Mints a Po token using a pre-initialized global minter. This function is asynchronous as the minter callback might return a Promise. It validates the identifier type, the minting result, and the token's format and size. ```javascript /** * Mints a poToken using the pre-created minter callback. * The minter MUST have been created during initialization via createPoTokenMinter(). * NOTE: mintCallback() may return a Promise, so this function is async. * @param identifier - The identifier to bind the token to (as Uint8Array) * @returns Promise containing the poToken */ async function obtainPoToken(identifier) { if (!poTokenMinter) { throw new Error('MNT:NotInit - poTokenMinter was not initialized. Call createPoTokenMinter first.'); } // Mint the token with the identifier // NOTE: mintCallback() may return a Promise, so we await it var result; try { var mintResult = poTokenMinter(identifier); // Handle both sync and async mintCallback if (mintResult && typeof mintResult.then === 'function') { console.log('` + '`' + 'obtainPoToken` mintCallback returned a Promise, awaiting...'); result = await mintResult; } else { result = mintResult; } } catch (e) { throw new Error('MNT:Failed - mintCallback() threw: ' + e.message); } if (!result) { throw new Error('YNJ:Undefined - mint result is undefined'); } if (!(result instanceof Uint8Array)) { throw new Error('ODM:Invalid - result is not Uint8Array, got: ' + Object.prototype.toString.call(result)); } // Validate token size (expected 110-128 bytes per BgUtils documentation) if (result.length < 100 || result.length > 140) { console.warn('` + '`' + 'obtainPoToken` Token size ' + result.length + ' bytes may be outside expected range (110-128)'); } return result; } ``` -------------------------------- ### Filter Video Songs from Queue Status Source: https://context7.com/vivizzz007/vivi-music/llms.txt Filter out video songs from the queue status, keeping only audio-only tracks. Uses the `disableVideos` parameter. ```kotlin val audioStatus = status.filterVideoSongs(disableVideos = true) ``` -------------------------------- ### Filter Explicit Tracks from Queue Status Source: https://context7.com/vivizzz007/vivi-music/llms.txt Filter explicit tracks from the current queue status. Takes a boolean `enabled` parameter to control filtering. ```kotlin val cleanStatus = status.filterExplicit(enabled = true) ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.