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 "androidx.work:work-rxjava3:${androidxWorkVersion}"
implementation 'com.google.dagger:hilt-android:2.52' implementation 'com.google.dagger:hilt-android:2.52'
kapt 'com.google.dagger:hilt-compiler: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' implementation 'com.google.android.material:material:1.11.0'
/** Third-party libraries **/ /** Third-party libraries **/

View File

@ -21,8 +21,14 @@
package net.newpipe.newplayer.testapp package net.newpipe.newplayer.testapp
import android.app.Application import android.app.Application
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.util.Log import android.util.Log
import androidx.annotation.OptIn
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import androidx.media3.common.MediaMetadata
import androidx.media3.common.util.UnstableApi
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -30,11 +36,19 @@ import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.newpipe.newplayer.NewPlayer import net.newpipe.newplayer.NewPlayer
import net.newpipe.newplayer.NewPlayerImpl 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.CachingRepository
import net.newpipe.newplayer.repository.PlaceHolderRepository import net.newpipe.newplayer.repository.MediaRepository
import net.newpipe.newplayer.repository.PrefetchingRepository import net.newpipe.newplayer.repository.PrefetchingRepository
import okhttp3.OkHttpClient
import okhttp3.Request
import org.schabi.newpipe.App import org.schabi.newpipe.App
import org.schabi.newpipe.MainActivity import org.schabi.newpipe.MainActivity
import javax.inject.Singleton import javax.inject.Singleton
@ -47,7 +61,7 @@ object NewPlayerComponent {
fun provideNewPlayer(app: Application): NewPlayer { fun provideNewPlayer(app: Application): NewPlayer {
val player = NewPlayerImpl( val player = NewPlayerImpl(
app = app, app = app,
repository = PrefetchingRepository(CachingRepository(PlaceHolderRepository())), repository = PrefetchingRepository(CachingRepository(TestMediaRepository())),
notificationIcon = IconCompat.createWithResource(app, net.newpipe.newplayer.R.drawable.new_player_tiny_icon), notificationIcon = IconCompat.createWithResource(app, net.newpipe.newplayer.R.drawable.new_player_tiny_icon),
playerActivityClass = MainActivity::class.java, playerActivityClass = MainActivity::class.java,
// rescueStreamFault = … // rescueStreamFault = …
@ -64,3 +78,83 @@ object NewPlayerComponent {
return player 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.bottomsheet.BottomSheetBehavior;
import com.google.android.material.tabs.TabLayout; 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.App;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.database.stream.model.StreamEntity;
@ -128,11 +131,15 @@ import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer; 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.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
@AndroidEntryPoint
public final class VideoDetailFragment public final class VideoDetailFragment
extends BaseStateFragment<StreamInfo> extends BaseStateFragment<StreamInfo>
implements BackPressable, implements BackPressable,
@ -230,6 +237,8 @@ public final class VideoDetailFragment
@Nullable @Nullable
private PlayerService playerService; private PlayerService playerService;
private Player player; private Player player;
@Inject
NewPlayer newPlayer;
private final PlayerHolder playerHolder = PlayerHolder.getInstance(); private final PlayerHolder playerHolder = PlayerHolder.getInstance();
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -1155,10 +1164,13 @@ public final class VideoDetailFragment
final PlayQueue queue = setupPlayQueueForIntent(false); final PlayQueue queue = setupPlayQueueForIntent(false);
tryAddVideoPlayerView(); tryAddVideoPlayerView();
newPlayer.playStream("bgp", PlayMode.EMBEDDED_VIDEO);
newPlayer.setPlayWhenReady(true);
newPlayer.prepare();
final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(), // final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(),
PlayerService.class, queue, true, autoPlayEnabled); // PlayerService.class, queue, true, autoPlayEnabled);
ContextCompat.startForegroundService(activity, playerIntent); // ContextCompat.startForegroundService(activity, playerIntent);
} }
/** /**
@ -1251,15 +1263,18 @@ public final class VideoDetailFragment
// setup the surface view height, so that it fits the video correctly // setup the surface view height, so that it fits the video correctly
setHeightThumbnail(); setHeightThumbnail();
player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { getLayoutInflater().inflate(
// sometimes binding would be null here, even though getView() != null above u.u R.layout.fragment_newplayer_view, binding.playerPlaceholder);
if (binding != null) {
// prevent from re-adding a view multiple times // player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> {
playerUi.removeViewFromParent(); // // sometimes binding would be null here, even though getView() != null above u.u
binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); // if (binding != null) {
playerUi.setupVideoSurfaceIfNeeded(); // // 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" />