diff --git a/app/build.gradle b/app/build.gradle index a9716fcbb..70b4a7158 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -243,6 +243,9 @@ dependencies { implementation "androidx.work:work-rxjava3:${androidxWorkVersion}" implementation 'com.google.dagger:hilt-android:2.52' kapt 'com.google.dagger:hilt-compiler:2.52' + implementation "androidx.media3:media3-common:1.3.1" + implementation "androidx.media3:media3-exoplayer:1.3.1" + implementation "androidx.media3:media3-ui:1.3.1" implementation 'com.google.android.material:material:1.11.0' /** Third-party libraries **/ diff --git a/app/src/main/java/org/schabi/newpipe/NewPlayerComponent.kt b/app/src/main/java/org/schabi/newpipe/NewPlayerComponent.kt index 77dcbdc4c..fa2cde676 100644 --- a/app/src/main/java/org/schabi/newpipe/NewPlayerComponent.kt +++ b/app/src/main/java/org/schabi/newpipe/NewPlayerComponent.kt @@ -21,8 +21,14 @@ package net.newpipe.newplayer.testapp import android.app.Application +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri import android.util.Log +import androidx.annotation.OptIn import androidx.core.graphics.drawable.IconCompat +import androidx.media3.common.MediaMetadata +import androidx.media3.common.util.UnstableApi import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -30,11 +36,19 @@ import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import net.newpipe.newplayer.NewPlayer import net.newpipe.newplayer.NewPlayerImpl +import net.newpipe.newplayer.data.AudioStreamTrack +import net.newpipe.newplayer.data.Chapter +import net.newpipe.newplayer.data.Stream +import net.newpipe.newplayer.data.Subtitle +import net.newpipe.newplayer.data.VideoStreamTrack import net.newpipe.newplayer.repository.CachingRepository -import net.newpipe.newplayer.repository.PlaceHolderRepository +import net.newpipe.newplayer.repository.MediaRepository import net.newpipe.newplayer.repository.PrefetchingRepository +import okhttp3.OkHttpClient +import okhttp3.Request import org.schabi.newpipe.App import org.schabi.newpipe.MainActivity import javax.inject.Singleton @@ -47,7 +61,7 @@ object NewPlayerComponent { fun provideNewPlayer(app: Application): NewPlayer { val player = NewPlayerImpl( app = app, - repository = PrefetchingRepository(CachingRepository(PlaceHolderRepository())), + repository = PrefetchingRepository(CachingRepository(TestMediaRepository())), notificationIcon = IconCompat.createWithResource(app, net.newpipe.newplayer.R.drawable.new_player_tiny_icon), playerActivityClass = MainActivity::class.java, // rescueStreamFault = … @@ -64,3 +78,83 @@ object NewPlayerComponent { return player } } + +class TestMediaRepository() : MediaRepository { + private val client = OkHttpClient() + + override fun getRepoInfo() = + MediaRepository.RepoMetaInfo(canHandleTimestampedLinks = true, pullsDataFromNetwork = true) + + @OptIn(UnstableApi::class) + override suspend fun getMetaInfo(item: String): MediaMetadata = + MediaMetadata.Builder() + .setTitle("BGP and the rule of bla") + .setArtist("mr BGP") + .setArtworkUri(Uri.parse("https://static.media.ccc.de/media/congress/2017/9072-hd.jpg")) + .setDurationMs( + 1871L * 1000L + ) + .build() + + override suspend fun getStreams(item: String): List { + return listOf( + Stream( + item = "bgp", + streamUri = Uri.parse("https://cdn.media.ccc.de/congress/2017/h264-hd/34c3-9072-eng-BGP_and_the_Rule_of_Custom.mp4"), + mimeType = null, + streamTracks = listOf( + AudioStreamTrack( + bitrate = 480000, + fileFormat = "MPEG4", + language = "en" + ), + VideoStreamTrack( + width = 1920, + height = 1080, + frameRate = 25, + fileFormat = "MPEG4" + ) + ) + ) + ) + } + + override suspend fun getSubtitles(item: String) = + emptyList() + + override suspend fun getPreviewThumbnail(item: String, timestampInMs: Long): Bitmap? { + + val templateUrl = "https://static.media.ccc.de/media/congress/2017/9072-hd.jpg" + + val thumbnailId = (timestampInMs / (10 * 1000)) + 1 + + if (getPreviewThumbnailsInfo(item).count < thumbnailId) { + return null + } + + val thumbUrl = String.format(templateUrl, thumbnailId) + + val bitmap = withContext(Dispatchers.IO) { + val request = Request.Builder().url(thumbUrl).build() + val response = client.newCall(request).execute() + try { + val responseBody = response.body + val bitmap = BitmapFactory.decodeStream(responseBody?.byteStream()) + return@withContext bitmap + } catch (e: Exception) { + return@withContext null + } + } + + return bitmap + } + + override suspend fun getPreviewThumbnailsInfo(item: String) = + MediaRepository.PreviewThumbnailsInfo(0, 0) + + override suspend fun getChapters(item: String) = + listOf() + + override suspend fun getTimestampLink(item: String, timestampInSeconds: Long) = + "" +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 1d1e166e7..bc3759c9e 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -63,6 +63,9 @@ import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.tabs.TabLayout; +import net.newpipe.newplayer.NewPlayer; +import net.newpipe.newplayer.data.PlayMode; + import org.schabi.newpipe.App; import org.schabi.newpipe.R; import org.schabi.newpipe.database.stream.model.StreamEntity; @@ -128,11 +131,15 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import javax.inject.Inject; + +import dagger.hilt.android.AndroidEntryPoint; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; +@AndroidEntryPoint public final class VideoDetailFragment extends BaseStateFragment implements BackPressable, @@ -230,6 +237,8 @@ public final class VideoDetailFragment @Nullable private PlayerService playerService; private Player player; + @Inject + NewPlayer newPlayer; private final PlayerHolder playerHolder = PlayerHolder.getInstance(); /*////////////////////////////////////////////////////////////////////////// @@ -1155,10 +1164,13 @@ public final class VideoDetailFragment final PlayQueue queue = setupPlayQueueForIntent(false); tryAddVideoPlayerView(); + newPlayer.playStream("bgp", PlayMode.EMBEDDED_VIDEO); + newPlayer.setPlayWhenReady(true); + newPlayer.prepare(); - final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(), - PlayerService.class, queue, true, autoPlayEnabled); - ContextCompat.startForegroundService(activity, playerIntent); +// final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(), +// PlayerService.class, queue, true, autoPlayEnabled); +// ContextCompat.startForegroundService(activity, playerIntent); } /** @@ -1251,15 +1263,18 @@ public final class VideoDetailFragment // setup the surface view height, so that it fits the video correctly setHeightThumbnail(); - player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { - // sometimes binding would be null here, even though getView() != null above u.u - if (binding != null) { - // prevent from re-adding a view multiple times - playerUi.removeViewFromParent(); - binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); - playerUi.setupVideoSurfaceIfNeeded(); - } - }); + getLayoutInflater().inflate( + R.layout.fragment_newplayer_view, binding.playerPlaceholder); + +// player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { +// // sometimes binding would be null here, even though getView() != null above u.u +// if (binding != null) { +// // prevent from re-adding a view multiple times +// playerUi.removeViewFromParent(); +// binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); +// playerUi.setupVideoSurfaceIfNeeded(); +// } +// }); }); } diff --git a/app/src/main/res/layout/fragment_newplayer_view.xml b/app/src/main/res/layout/fragment_newplayer_view.xml new file mode 100644 index 000000000..a3a00d91d --- /dev/null +++ b/app/src/main/res/layout/fragment_newplayer_view.xml @@ -0,0 +1,7 @@ + +