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 4604a89137
commit d9d6a5609c
4 changed files with 133 additions and 14 deletions

View File

@ -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 **/

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.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<StreamInfo>
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();
// }
// });
});
}

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" />