diff --git a/android/mediaprovider/core/src/main/java/com/simplecityapps/mediaprovider/MediaImporter.kt b/android/mediaprovider/core/src/main/java/com/simplecityapps/mediaprovider/MediaImporter.kt index 13338cf0..d3810f9d 100644 --- a/android/mediaprovider/core/src/main/java/com/simplecityapps/mediaprovider/MediaImporter.kt +++ b/android/mediaprovider/core/src/main/java/com/simplecityapps/mediaprovider/MediaImporter.kt @@ -81,7 +81,7 @@ class MediaImporter( return } - Timber.v("Starting import..") + Timber.v("Starting import...") val time = System.currentTimeMillis() isImporting = true diff --git a/android/mediaprovider/core/src/main/java/com/simplecityapps/mediaprovider/repository/playlists/PlaylistRepository.kt b/android/mediaprovider/core/src/main/java/com/simplecityapps/mediaprovider/repository/playlists/PlaylistRepository.kt index eb30effb..bbba47f8 100644 --- a/android/mediaprovider/core/src/main/java/com/simplecityapps/mediaprovider/repository/playlists/PlaylistRepository.kt +++ b/android/mediaprovider/core/src/main/java/com/simplecityapps/mediaprovider/repository/playlists/PlaylistRepository.kt @@ -6,6 +6,7 @@ import com.simplecityapps.shuttle.model.PlaylistSong import com.simplecityapps.shuttle.model.SmartPlaylist import com.simplecityapps.shuttle.model.Song import com.simplecityapps.shuttle.sorting.PlaylistSongSortOrder +import java.io.OutputStream import java.io.Serializable import kotlinx.coroutines.flow.Flow @@ -70,6 +71,8 @@ interface PlaylistRepository { playlist: Playlist, externalId: String? ) + + suspend fun updateM3uFile(playlist: Playlist) } enum class PlaylistSortOrder : Serializable { diff --git a/android/mediaprovider/local/src/main/java/com/simplecityapps/localmediaprovider/local/provider/taglib/TaglibMediaProvider.kt b/android/mediaprovider/local/src/main/java/com/simplecityapps/localmediaprovider/local/provider/taglib/TaglibMediaProvider.kt index f4ea48e7..16bd87d8 100644 --- a/android/mediaprovider/local/src/main/java/com/simplecityapps/localmediaprovider/local/provider/taglib/TaglibMediaProvider.kt +++ b/android/mediaprovider/local/src/main/java/com/simplecityapps/localmediaprovider/local/provider/taglib/TaglibMediaProvider.kt @@ -150,7 +150,7 @@ class TaglibMediaProvider( mediaProviderType = type, name = m3uPlaylist.name, songs = songs, - externalId = m3uPlaylist.name + externalId = m3uPlaylist.path ) updateData } else { diff --git a/android/mediaprovider/local/src/main/java/com/simplecityapps/localmediaprovider/local/repository/LocalPlaylistRepository.kt b/android/mediaprovider/local/src/main/java/com/simplecityapps/localmediaprovider/local/repository/LocalPlaylistRepository.kt index ca848e73..65656f1b 100644 --- a/android/mediaprovider/local/src/main/java/com/simplecityapps/localmediaprovider/local/repository/LocalPlaylistRepository.kt +++ b/android/mediaprovider/local/src/main/java/com/simplecityapps/localmediaprovider/local/repository/LocalPlaylistRepository.kt @@ -1,6 +1,7 @@ package com.simplecityapps.localmediaprovider.local.repository import android.content.Context +import android.net.Uri import com.simplecityapps.localmediaprovider.local.data.room.dao.PlaylistDataDao import com.simplecityapps.localmediaprovider.local.data.room.dao.PlaylistSongJoinDao import com.simplecityapps.localmediaprovider.local.data.room.entity.PlaylistData @@ -109,31 +110,40 @@ class LocalPlaylistRepository( override suspend fun addToPlaylist( playlist: Playlist, songs: List - ) = playlistSongJoinDao.insert( - songs.mapIndexed { i, song -> - PlaylistSongJoin( - playlistId = playlist.id, - songId = song.id, - sortOrder = (playlist.songCount + i).toLong() - ) - } - ) + ) { + playlistSongJoinDao.insert( + songs.mapIndexed { i, song -> + PlaylistSongJoin( + playlistId = playlist.id, + songId = song.id, + sortOrder = (playlist.songCount + i).toLong() + ) + } + ) + updateM3uFile(playlist) + } override suspend fun removeFromPlaylist( playlist: Playlist, playlistSongs: List - ) = playlistSongJoinDao.delete( - playlistId = playlist.id, - playlistSongIds = playlistSongs.map { playlistSong -> playlistSong.id }.toTypedArray() - ) + ) { + playlistSongJoinDao.delete( + playlistId = playlist.id, + playlistSongIds = playlistSongs.map { playlistSong -> playlistSong.id }.toTypedArray() + ) + updateM3uFile(playlist) + } override suspend fun removeSongsFromPlaylist( playlist: Playlist, songs: List - ) = playlistSongJoinDao.deleteSongs( - playlistId = playlist.id, - songIds = songs.map { it.id }.toTypedArray() - ) + ) { + playlistSongJoinDao.deleteSongs( + playlistId = playlist.id, + songIds = songs.map { it.id }.toTypedArray() + ) + updateM3uFile(playlist) + } override fun getSongsForPlaylist(playlist: Playlist): Flow> = playlistSongJoinDao.getSongsForPlaylist(playlist.id) .map { playlistSong -> @@ -144,7 +154,10 @@ class LocalPlaylistRepository( override suspend fun deleteAll(mediaProviderType: MediaProviderType) = playlistDataDao.deleteAll(mediaProviderType) - override suspend fun clearPlaylist(playlist: Playlist) = playlistDataDao.clear(playlist.id) + override suspend fun clearPlaylist(playlist: Playlist) { + playlistDataDao.clear(playlist.id) + updateM3uFile(playlist) + } override suspend fun renamePlaylist( playlist: Playlist, @@ -159,6 +172,32 @@ class LocalPlaylistRepository( ) ) + override suspend fun updateM3uFile(playlist: Playlist) { + val outputStream = playlist.externalId?.let { path -> + context.contentResolver.openOutputStream(Uri.parse(playlist.externalId), "wt") + } + + if (outputStream == null) { + Timber.w("Unable to open M3U file at ${playlist.externalId} for playlist ${playlist.name}") + } else { + val playlistPath = Uri.decode(playlist.externalId?: "") + val playlistFolder = playlistPath.substringBeforeLast("/") + "/" + + getSongsForPlaylist(playlist) + .firstOrNull() + .orEmpty() + .forEach { plSong -> + // Quick-and-dirty way to relativize the song path to the m3u folder + // Note that paths can be content:// URIs, for which there is no proper .relativize() method + // We'll use absolute values (paths or URIs, whatever is in database) for files that are not stored in a sub-folder relative to the M3U file + val songPath = Uri.decode(plSong.song.path) + val relative = songPath.substringAfter(playlistFolder) + val line = relative.toByteArray() + /* CRLF */ 0x0d.toByte() + 0x0A.toByte() + outputStream.write(line) + } + } + } + override suspend fun updatePlaylistSortOder( playlist: Playlist, sortOrder: PlaylistSongSortOrder @@ -189,6 +228,7 @@ class LocalPlaylistRepository( } } ) + updateM3uFile(playlist) } override suspend fun updatePlaylistMediaProviderType(