WIP: Play a simple media.ccc stream from the video fragment

This barely works, if you click on any video it should start playing a
media.ccc.de stream, but it does not display anything in the video
view yet.
This commit is contained in:
Profpatsch 2024-12-04 17:32:20 +01:00
parent 16b372dece
commit 4409a990de
5 changed files with 137 additions and 14 deletions

View File

@ -229,6 +229,9 @@ dependencies {
implementation libs.androidx.work.runtime
implementation libs.androidx.work.rxjava3
implementation libs.androidx.material
implementation libs.androidx.media3.common
implementation libs.androidx.media3.exoplayer
implementation libs.androidx.media3.ui
/** Third-party libraries **/
// Instance state boilerplate elimination

View File

@ -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<Stream> {
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<Subtitle>()
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<Chapter>()
override suspend fun getTimestampLink(item: String, timestampInSeconds: Long) =
""
}

View File

@ -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.concurrent.TimeUnit;
import java.util.function.Consumer;
import coil3.util.CoilUtils;
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<StreamInfo>
implements BackPressable,
@ -228,6 +235,8 @@ public final class VideoDetailFragment
@Nullable
private PlayerService playerService;
private Player player;
@Inject
NewPlayer newPlayer;
private final PlayerHolder playerHolder = PlayerHolder.getInstance();
/*//////////////////////////////////////////////////////////////////////////
@ -1138,10 +1147,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);
}
/**
@ -1234,15 +1246,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();
// }
// });
});
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<net.newpipe.newplayer.ui.NewPlayerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/embedded_player_newplayer"
android:name="net.newpipe.newplayer.VideoPlayerFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="50dp" />

View File

@ -32,6 +32,7 @@ localbroadcastmanager = "1.1.0"
markwon = "4.6.2"
material = "1.11.0"
media = "1.7.0"
media3 = "1.3.1"
mockitoCore = "5.6.0"
navigationCompose = "2.8.3"
okhttp = "4.12.0"
@ -96,6 +97,9 @@ androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "l
androidx-localbroadcastmanager = { group = "androidx.localbroadcastmanager", name = "localbroadcastmanager", version.ref = "localbroadcastmanager" }
androidx-material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-media = { group = "androidx.media", name = "media", version.ref = "media" }
androidx-media3-common = { group = "androidx.media3", name = "media3-common", version.ref = "media3" }
androidx-media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" }
androidx-media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media3" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
androidx-paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "pagingCompose" }
androidx-preference = { group = "androidx.preference", name = "preference", version.ref = "preference" }