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:
parent
4604a89137
commit
d9d6a5609c
|
@ -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 **/
|
||||||
|
|
|
@ -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) =
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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" />
|
Loading…
Reference in New Issue