diff --git a/.kotlin/errors/errors-1725898521690.log b/.kotlin/errors/errors-1725898521690.log new file mode 100644 index 0000000..a60cc05 --- /dev/null +++ b/.kotlin/errors/errors-1725898521690.log @@ -0,0 +1,91 @@ +kotlin version: 2.0.20-Beta2 +error message: java.lang.IllegalStateException: Storage for [/home/schabi/Projects/NewPlayer/new-player/build/kspCaches/debug/symbolLookups/file-to-id.tab] is already registered + at org.jetbrains.kotlin.com.intellij.util.io.FilePageCache.registerPagedFileStorage(FilePageCache.java:410) + at org.jetbrains.kotlin.com.intellij.util.io.PagedFileStorage.(PagedFileStorage.java:72) + at org.jetbrains.kotlin.com.intellij.util.io.ResizeableMappedFile.(ResizeableMappedFile.java:55) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentBTreeEnumerator.(PersistentBTreeEnumerator.java:128) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentEnumerator.createDefaultEnumerator(PersistentEnumerator.java:52) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.(PersistentMapImpl.java:165) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.(PersistentMapImpl.java:140) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapBuilder.buildImplementation(PersistentMapBuilder.java:88) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapBuilder.build(PersistentMapBuilder.java:71) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.(PersistentHashMap.java:45) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.(PersistentHashMap.java:71) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.createMap(LazyStorage.kt:60) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.getStorageOrCreateNew(LazyStorage.kt:57) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:78) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.set(PersistentStorage.kt:94) + at org.jetbrains.kotlin.incremental.LookupStorage.addFileIfNeeded(LookupStorage.kt:164) + at org.jetbrains.kotlin.incremental.LookupStorage.addAll$lambda$4(LookupStorage.kt:117) + at org.jetbrains.kotlin.utils.CollectionsKt.keysToMap(collections.kt:117) + at org.jetbrains.kotlin.incremental.LookupStorage.addAll(LookupStorage.kt:117) + at org.jetbrains.kotlin.incremental.BuildUtilKt.update(buildUtil.kt:134) + at com.google.devtools.ksp.LookupStorageWrapperImpl.update(IncrementalContext.kt:231) + at com.google.devtools.ksp.common.IncrementalContextBase.updateLookupCache(IncrementalContextBase.kt:133) + at com.google.devtools.ksp.common.IncrementalContextBase.updateCaches(IncrementalContextBase.kt:364) + at com.google.devtools.ksp.common.IncrementalContextBase.updateCachesAndOutputs(IncrementalContextBase.kt:470) + at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:362) + at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:112) + at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373) + at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.repeatAnalysisIfNeeded(KotlinToJVMBytecodeCompiler.kt:282) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.repeatAnalysisIfNeeded(KotlinToJVMBytecodeCompiler.kt:282) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49) + at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1556) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) + at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.base/java.lang.reflect.Method.invoke(Method.java:568) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:712) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:399) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) + at java.base/java.lang.Thread.run(Thread.java:840) + Suppressed: java.lang.Exception: Storage[/home/schabi/Projects/NewPlayer/new-player/build/kspCaches/debug/symbolLookups/file-to-id.tab] registration stack trace + at org.jetbrains.kotlin.com.intellij.util.io.FilePageCache.registerPagedFileStorage(FilePageCache.java:437) + at org.jetbrains.kotlin.com.intellij.util.io.PagedFileStorage.(PagedFileStorage.java:72) + at org.jetbrains.kotlin.com.intellij.util.io.ResizeableMappedFile.(ResizeableMappedFile.java:55) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentBTreeEnumerator.(PersistentBTreeEnumerator.java:128) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentEnumerator.createDefaultEnumerator(PersistentEnumerator.java:52) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.(PersistentMapImpl.java:165) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.(PersistentMapImpl.java:140) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapBuilder.buildImplementation(PersistentMapBuilder.java:88) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapBuilder.build(PersistentMapBuilder.java:71) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.(PersistentHashMap.java:45) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.(PersistentHashMap.java:71) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.createMap(LazyStorage.kt:60) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.getStorageIfExists(LazyStorage.kt:51) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.get(LazyStorage.kt:74) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.get(PersistentStorage.kt:90) + at org.jetbrains.kotlin.incremental.LookupStorage.removeLookupsFrom(LookupStorage.kt:131) + at com.google.devtools.ksp.LookupStorageWrapperImpl.removeLookupsFrom(IncrementalContext.kt:234) + at com.google.devtools.ksp.common.IncrementalContextBase.updateFromRemovedOutputs(IncrementalContextBase.kt:122) + at com.google.devtools.ksp.common.IncrementalContextBase.calcDirtyFiles(IncrementalContextBase.kt:277) + at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$2.invoke(KotlinSymbolProcessingExtension.kt:196) + at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$2.invoke(KotlinSymbolProcessingExtension.kt:189) + at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:414) + at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:189) + at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:112) + at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373) + at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364) + ... 25 more + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a2c1392..6075a8a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,6 +44,7 @@ newplayer = "master-SNAPSHOT" okhttpAndroid = "5.0.0-alpha.14" coil = "2.7.0" reorderable = "2.4.0-alpha02" +media3Session = "1.4.1" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -79,6 +80,7 @@ newplayer = { group = "com.github.theScrabi.NewPlayer", name = "new-player", ver okhttp-android = { group = "com.squareup.okhttp3", name = "okhttp-android", version.ref = "okhttpAndroid" } coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" } reorderable = { group = "sh.calvin.reorderable", name = "reorderable", version.ref = "reorderable" } +androidx-media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3Session" } diff --git a/new-player/build.gradle.kts b/new-player/build.gradle.kts index 65288de..20aa232 100644 --- a/new-player/build.gradle.kts +++ b/new-player/build.gradle.kts @@ -68,6 +68,7 @@ dependencies { implementation(libs.androidx.media3.common) implementation(libs.coil.compose) implementation(libs.reorderable) + implementation(libs.androidx.media3.session) ksp(libs.hilt.android.compiler) ksp(libs.androidx.hilt.compiler) diff --git a/new-player/src/main/AndroidManifest.xml b/new-player/src/main/AndroidManifest.xml index e6881f6..fec1d41 100644 --- a/new-player/src/main/AndroidManifest.xml +++ b/new-player/src/main/AndroidManifest.xml @@ -1,11 +1,16 @@ + + - + + + + + - \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt b/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt index 3bb436d..725787f 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt @@ -37,13 +37,11 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import net.newpipe.newplayer.model.UIModeState -import net.newpipe.newplayer.playerInternals.PlaylistItem -import net.newpipe.newplayer.playerInternals.fetchPlaylistItem -import net.newpipe.newplayer.playerInternals.getPlaylistItemsFromExoplayer +import net.newpipe.newplayer.model.PlaylistItem +import net.newpipe.newplayer.model.fetchPlaylistItem +import net.newpipe.newplayer.model.getPlaylistItemsFromExoplayer import kotlin.Exception import kotlin.random.Random @@ -62,8 +60,6 @@ enum class RepeatMode { REPEAT_ONE } -private val TAG = "NewPlayer" - interface NewPlayer { // preferences val preferredStreamVariants: List @@ -72,7 +68,6 @@ interface NewPlayer { var playWhenReady: Boolean val duration: Long val bufferedPercentage: Int - val sharingLinkWithOffsetPossible: Boolean var currentPosition: Long var fastSeekAmountSec: Int val playBackMode: MutableStateFlow @@ -100,11 +95,11 @@ interface NewPlayer { fun playStream(item: String, playMode: PlayMode) fun selectChapter(index: Int) fun playStream(item: String, streamVariant: String, playMode: PlayMode) + fun release() data class Builder(val app: Application, val repository: MediaRepository) { private var mediaSourceFactory: MediaSource.Factory? = null private var preferredStreamVariants: List = emptyList() - private var sharingLinkWithOffsetPossible = false fun setMediaSourceFactory(mediaSourceFactory: MediaSource.Factory): Builder { this.mediaSourceFactory = mediaSourceFactory @@ -116,11 +111,6 @@ interface NewPlayer { return this } - fun setSharingLinkWithOffsetPossible(possible: Boolean): Builder { - this.sharingLinkWithOffsetPossible = false - return this - } - fun build(): NewPlayer { val exoPlayerBuilder = ExoPlayer.Builder(app) mediaSourceFactory?.let { @@ -131,284 +121,8 @@ interface NewPlayer { internalPlayer = exoPlayerBuilder.build(), repository = repository, preferredStreamVariants = preferredStreamVariants, - sharingLinkWithOffsetPossible = sharingLinkWithOffsetPossible ) } } } - -class NewPlayerImpl( - val app: Application, - override val internalPlayer: Player, - override val preferredStreamVariants: List, - private val repository: MediaRepository, - override val sharingLinkWithOffsetPossible: Boolean -) : NewPlayer { - - private var uniqueIdToIdLookup = HashMap() - - var mutableErrorFlow = MutableSharedFlow() - override val errorFlow = mutableErrorFlow.asSharedFlow() - - override val bufferedPercentage: Int - get() = internalPlayer.bufferedPercentage - - override var currentPosition: Long - get() = internalPlayer.currentPosition - set(value) { - internalPlayer.seekTo(value) - } - - override var fastSeekAmountSec: Int = 10 - - private var playerScope = CoroutineScope(Dispatchers.Main + Job()) - - override var playBackMode = MutableStateFlow(PlayMode.IDLE) - - override var shuffle: Boolean - get() = internalPlayer.shuffleModeEnabled - set(value) { - internalPlayer.shuffleModeEnabled = value - } - - override var repeatMode: RepeatMode - get() = when (internalPlayer.repeatMode) { - Player.REPEAT_MODE_OFF -> RepeatMode.DONT_REPEAT - Player.REPEAT_MODE_ALL -> RepeatMode.REPEAT_ALL - Player.REPEAT_MODE_ONE -> RepeatMode.REPEAT_ONE - else -> throw NewPlayerException("Unknown Repeatmode option returned by ExoPlayer: ${internalPlayer.repeatMode}") - } - set(value) { - when (value) { - RepeatMode.DONT_REPEAT -> internalPlayer.repeatMode = Player.REPEAT_MODE_OFF - RepeatMode.REPEAT_ALL -> internalPlayer.repeatMode = Player.REPEAT_MODE_ALL - RepeatMode.REPEAT_ONE -> internalPlayer.repeatMode = Player.REPEAT_MODE_ONE - } - } - - private var mutableOnEvent = MutableSharedFlow>() - override val onExoPlayerEvent: SharedFlow> = - mutableOnEvent.asSharedFlow() - - override var playWhenReady: Boolean - set(value) { - internalPlayer.playWhenReady = value - } - get() = internalPlayer.playWhenReady - - - override val duration: Long - get() = internalPlayer.duration - - private val mutablePlaylist = MutableStateFlow>(emptyList()) - override val playlist: StateFlow> = - mutablePlaylist.asStateFlow() - - private val mutableCurrentlyPlaying = MutableStateFlow(null) - override val currentlyPlaying: StateFlow = mutableCurrentlyPlaying.asStateFlow() - - private val mutableCurrentChapter = MutableStateFlow>(emptyList()) - override val currentChapters: StateFlow> = mutableCurrentChapter.asStateFlow() - - override var currentlyPlayingPlaylistItem: Int - get() = internalPlayer.currentMediaItemIndex - set(value) { - assert(value in 0.. - playing?.let { - try { - val chapters = repository.getChapters(playing.id) - mutableCurrentChapter.update { chapters } - } catch (e: Exception) { - mutableErrorFlow.emit(e) - } - } - } - } - } - - private fun updatePlaylistItems() { - if (internalPlayer.mediaItemCount == 0) { - playBackMode.update { - PlayMode.IDLE - } - } - playerScope.launch { - val playlist = - getPlaylistItemsFromExoplayer(internalPlayer, repository, uniqueIdToIdLookup) - var playlistDuration = 0 - for (item in playlist) { - playlistDuration += item.lengthInS - } - - mutablePlaylist.update { - playlist - } - } - } - - private fun getPlaylistItem(uniqueId: Long): PlaylistItem? { - for (item in playlist.value) { - if (item.uniqueId == uniqueId) { - return item - } - } - return null - } - - override fun prepare() { - internalPlayer.prepare() - } - - override fun play() { - if (internalPlayer.currentMediaItem != null) { - internalPlayer.play() - } else { - Log.i(TAG, "Tried to start playing but no media Item was cued") - } - } - - override fun pause() { - internalPlayer.pause() - } - - override fun addToPlaylist(item: String) { - launchJobAndCollectError { - val mediaItem = toMediaItem(item) - internalPlayer.addMediaItem(mediaItem) - } - } - - override fun movePlaylistItem(fromIndex: Int, toIndex: Int) { - internalPlayer.moveMediaItem(fromIndex, toIndex) - } - - override fun removePlaylistItem(uniqueId: Long) { - for(i in 0.. Unit) = - playerScope.launch { - try { - task() - } catch (e: Exception) { - mutableErrorFlow.emit(e) - } - } - -} \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/NewPlayerImpl.kt b/new-player/src/main/java/net/newpipe/newplayer/NewPlayerImpl.kt new file mode 100644 index 0000000..93488ad --- /dev/null +++ b/new-player/src/main/java/net/newpipe/newplayer/NewPlayerImpl.kt @@ -0,0 +1,341 @@ +/* NewPlayer + * + * @author Christian Schabesberger + * + * Copyright (C) NewPipe e.V. 2024 + * + * NewPlayer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPlayer. If not, see . + */ + +package net.newpipe.newplayer + +import android.app.Application +import android.content.ComponentName +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.media3.common.MediaItem +import androidx.media3.common.PlaybackException +import androidx.media3.common.Player +import androidx.media3.common.Timeline +import androidx.media3.session.MediaController +import androidx.media3.session.SessionToken +import com.google.common.util.concurrent.MoreExecutors +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import net.newpipe.newplayer.model.PlaylistItem +import net.newpipe.newplayer.model.fetchPlaylistItem +import net.newpipe.newplayer.model.getPlaylistItemsFromExoplayer +import net.newpipe.newplayer.service.NewPlayerService +import kotlin.random.Random + +private const val TAG = "NewPlayerImpl" + +class NewPlayerImpl( + val app: Application, + override val internalPlayer: Player, + override val preferredStreamVariants: List, + private val repository: MediaRepository +) : NewPlayer { + + private var uniqueIdToIdLookup = HashMap() + + // this is used to take care of the NewPlayerService + private var mediaController: MediaController? = null + + var mutableErrorFlow = MutableSharedFlow() + override val errorFlow = mutableErrorFlow.asSharedFlow() + + override val bufferedPercentage: Int + get() = internalPlayer.bufferedPercentage + + override var currentPosition: Long + get() = internalPlayer.currentPosition + set(value) { + internalPlayer.seekTo(value) + } + + override var fastSeekAmountSec: Int = 10 + + private var playerScope = CoroutineScope(Dispatchers.Main + Job()) + + override var playBackMode = MutableStateFlow(PlayMode.IDLE) + + override var shuffle: Boolean + get() = internalPlayer.shuffleModeEnabled + set(value) { + internalPlayer.shuffleModeEnabled = value + } + + override var repeatMode: RepeatMode + get() = when (internalPlayer.repeatMode) { + Player.REPEAT_MODE_OFF -> RepeatMode.DONT_REPEAT + Player.REPEAT_MODE_ALL -> RepeatMode.REPEAT_ALL + Player.REPEAT_MODE_ONE -> RepeatMode.REPEAT_ONE + else -> throw NewPlayerException("Unknown Repeatmode option returned by ExoPlayer: ${internalPlayer.repeatMode}") + } + set(value) { + when (value) { + RepeatMode.DONT_REPEAT -> internalPlayer.repeatMode = Player.REPEAT_MODE_OFF + RepeatMode.REPEAT_ALL -> internalPlayer.repeatMode = Player.REPEAT_MODE_ALL + RepeatMode.REPEAT_ONE -> internalPlayer.repeatMode = Player.REPEAT_MODE_ONE + } + } + + private var mutableOnEvent = MutableSharedFlow>() + override val onExoPlayerEvent: SharedFlow> = + mutableOnEvent.asSharedFlow() + + override var playWhenReady: Boolean + set(value) { + internalPlayer.playWhenReady = value + } + get() = internalPlayer.playWhenReady + + + override val duration: Long + get() = internalPlayer.duration + + private val mutablePlaylist = MutableStateFlow>(emptyList()) + override val playlist: StateFlow> = + mutablePlaylist.asStateFlow() + + private val mutableCurrentlyPlaying = MutableStateFlow(null) + override val currentlyPlaying: StateFlow = mutableCurrentlyPlaying.asStateFlow() + + private val mutableCurrentChapter = MutableStateFlow>(emptyList()) + override val currentChapters: StateFlow> = mutableCurrentChapter.asStateFlow() + + override var currentlyPlayingPlaylistItem: Int + get() = internalPlayer.currentMediaItemIndex + set(value) { + assert(value in 0.. + playing?.let { + try { + val chapters = repository.getChapters(playing.id) + mutableCurrentChapter.update { chapters } + } catch (e: Exception) { + mutableErrorFlow.emit(e) + } + } + } + } + } + + private fun updatePlaylistItems() { + if (internalPlayer.mediaItemCount == 0) { + playBackMode.update { + PlayMode.IDLE + } + } + playerScope.launch { + val playlist = + getPlaylistItemsFromExoplayer(internalPlayer, repository, uniqueIdToIdLookup) + var playlistDuration = 0 + for (item in playlist) { + playlistDuration += item.lengthInS + } + + mutablePlaylist.update { + playlist + } + } + } + + private fun getPlaylistItem(uniqueId: Long): PlaylistItem? { + for (item in playlist.value) { + if (item.uniqueId == uniqueId) { + return item + } + } + return null + } + + override fun prepare() { + internalPlayer.prepare() + if (mediaController == null) { + val sessionToken = SessionToken(app, ComponentName(app, NewPlayerService::class.java)) + val mediaControllerFuture = MediaController.Builder(app, sessionToken).buildAsync() + mediaControllerFuture.addListener({ + mediaController = mediaControllerFuture.get() + }, MoreExecutors.directExecutor()) + } + } + + + override fun play() { + if (internalPlayer.currentMediaItem != null) { + internalPlayer.play() + } else { + Log.i(TAG, "Tried to start playing but no media Item was cued") + } + } + + override fun pause() { + internalPlayer.pause() + } + + override fun addToPlaylist(item: String) { + launchJobAndCollectError { + val mediaItem = toMediaItem(item) + internalPlayer.addMediaItem(mediaItem) + } + } + + override fun movePlaylistItem(fromIndex: Int, toIndex: Int) { + internalPlayer.moveMediaItem(fromIndex, toIndex) + } + + override fun removePlaylistItem(uniqueId: Long) { + for (i in 0.. Unit) = + playerScope.launch { + try { + task() + } catch (e: Exception) { + mutableErrorFlow.emit(e) + } + } + +} \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/playerInternals/ChapterItem.kt b/new-player/src/main/java/net/newpipe/newplayer/model/ChapterItem.kt similarity index 95% rename from new-player/src/main/java/net/newpipe/newplayer/playerInternals/ChapterItem.kt rename to new-player/src/main/java/net/newpipe/newplayer/model/ChapterItem.kt index 3017dc0..5ea9c73 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/playerInternals/ChapterItem.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/ChapterItem.kt @@ -21,7 +21,7 @@ -package net.newpipe.newplayer.playerInternals +package net.newpipe.newplayer.model import net.newpipe.newplayer.utils.Thumbnail diff --git a/new-player/src/main/java/net/newpipe/newplayer/playerInternals/PlayListItem.kt b/new-player/src/main/java/net/newpipe/newplayer/model/PlayListItem.kt similarity index 96% rename from new-player/src/main/java/net/newpipe/newplayer/playerInternals/PlayListItem.kt rename to new-player/src/main/java/net/newpipe/newplayer/model/PlayListItem.kt index 20d0069..0e62da8 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/playerInternals/PlayListItem.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/PlayListItem.kt @@ -19,9 +19,8 @@ * */ -package net.newpipe.newplayer.playerInternals +package net.newpipe.newplayer.model -import androidx.media3.common.MediaItem import androidx.media3.common.Player import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async @@ -29,7 +28,6 @@ import net.newpipe.newplayer.MediaRepository import net.newpipe.newplayer.NewPlayerException import net.newpipe.newplayer.utils.Thumbnail import kotlin.coroutines.coroutineContext -import kotlin.random.Random data class PlaylistItem( val title: String, diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerUIState.kt b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerUIState.kt index 5b48050..889658d 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerUIState.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerUIState.kt @@ -22,7 +22,6 @@ package net.newpipe.newplayer.model import net.newpipe.newplayer.Chapter import net.newpipe.newplayer.RepeatMode -import net.newpipe.newplayer.playerInternals.PlaylistItem import net.newpipe.newplayer.ui.ContentScale data class VideoPlayerUIState( diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt index 53758d7..64b22dc 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt @@ -45,7 +45,6 @@ import kotlinx.coroutines.launch import net.newpipe.newplayer.utils.VideoSize import net.newpipe.newplayer.NewPlayer import net.newpipe.newplayer.RepeatMode -import net.newpipe.newplayer.playerInternals.PlaylistItem import net.newpipe.newplayer.ui.ContentScale import java.util.LinkedList diff --git a/new-player/src/main/java/net/newpipe/newplayer/service/NewPlayerService.kt b/new-player/src/main/java/net/newpipe/newplayer/service/NewPlayerService.kt new file mode 100644 index 0000000..88f8270 --- /dev/null +++ b/new-player/src/main/java/net/newpipe/newplayer/service/NewPlayerService.kt @@ -0,0 +1,52 @@ +/* NewPlayer + * + * @author Christian Schabesberger + * + * Copyright (C) NewPipe e.V. 2024 + * + * NewPlayer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPlayer. If not, see . + */ + + +package net.newpipe.newplayer.service + +import androidx.media3.session.MediaSession +import androidx.media3.session.MediaSessionService +import dagger.hilt.android.AndroidEntryPoint +import net.newpipe.newplayer.NewPlayer +import javax.inject.Inject + +@AndroidEntryPoint +class NewPlayerService : MediaSessionService() { + + private var mediaSession: MediaSession? = null + + @Inject + lateinit var newPlayer: NewPlayer + + override fun onDestroy() { + super.onDestroy() + newPlayer.release() + mediaSession?.release() + mediaSession = null + } + + override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? { + println("gurken get session") + if (mediaSession == null) { + mediaSession = MediaSession.Builder(this, newPlayer.internalPlayer).build() + } + return mediaSession + } +} \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/StreamSelectUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/StreamSelectUI.kt index bf2ed7b..81c26b3 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/StreamSelectUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/StreamSelectUI.kt @@ -41,8 +41,7 @@ import net.newpipe.newplayer.model.VideoPlayerUIState import net.newpipe.newplayer.model.VideoPlayerViewModel import net.newpipe.newplayer.model.VideoPlayerViewModelDummy import net.newpipe.newplayer.ui.theme.VideoPlayerTheme -import net.newpipe.newplayer.Chapter -import net.newpipe.newplayer.playerInternals.PlaylistItem +import net.newpipe.newplayer.model.PlaylistItem import net.newpipe.newplayer.ui.STREAMSELECT_UI_BACKGROUND_COLOR import net.newpipe.newplayer.ui.videoplayer.streamselect.ChapterItem import net.newpipe.newplayer.ui.videoplayer.streamselect.ChapterSelectTopBar diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/streamselect/StreamItem.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/streamselect/StreamItem.kt index a11ba3b..e1b8fef 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/streamselect/StreamItem.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/streamselect/StreamItem.kt @@ -20,13 +20,9 @@ package net.newpipe.newplayer.ui.videoplayer.streamselect -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box @@ -40,7 +36,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.DragHandle import androidx.compose.material3.ExperimentalMaterial3Api @@ -53,33 +48,25 @@ import androidx.compose.material3.SwipeToDismissBoxValue import androidx.compose.material3.Text import androidx.compose.material3.rememberSwipeToDismissBoxState import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import coil.compose.AsyncImage import net.newpipe.newplayer.R -import net.newpipe.newplayer.playerInternals.PlaylistItem +import net.newpipe.newplayer.model.PlaylistItem import net.newpipe.newplayer.ui.CONTROLLER_UI_BACKGROUND_COLOR import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.ui.videoplayer.ITEM_CORNER_SHAPE -import net.newpipe.newplayer.ui.videoplayer.gesture_ui.SEEK_ANIMATION_FADE_IN -import net.newpipe.newplayer.ui.videoplayer.gesture_ui.SEEK_ANIMATION_FADE_OUT -import net.newpipe.newplayer.utils.BitmapThumbnail -import net.newpipe.newplayer.utils.OnlineThumbnail import net.newpipe.newplayer.utils.ReorderHapticFeedback import net.newpipe.newplayer.utils.ReorderHapticFeedbackType import net.newpipe.newplayer.utils.Thumbnail -import net.newpipe.newplayer.utils.VectorThumbnail import net.newpipe.newplayer.utils.getLocale import net.newpipe.newplayer.utils.getTimeStringFromMs import sh.calvin.reorderable.ReorderableCollectionItemScope diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/streamselect/StreamSelectTopBar.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/streamselect/StreamSelectTopBar.kt index add428e..a5525db 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/streamselect/StreamSelectTopBar.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/streamselect/StreamSelectTopBar.kt @@ -47,7 +47,7 @@ import net.newpipe.newplayer.RepeatMode import net.newpipe.newplayer.model.VideoPlayerUIState import net.newpipe.newplayer.model.VideoPlayerViewModel import net.newpipe.newplayer.model.VideoPlayerViewModelDummy -import net.newpipe.newplayer.playerInternals.getPlaylistDurationInS +import net.newpipe.newplayer.model.getPlaylistDurationInS import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.utils.getLocale import net.newpipe.newplayer.utils.getTimeStringFromMs