WIP: initial repository setup for media.ccc streams
This uses the media.ccc.de URL as item-ID and the actual extractor to fetch the streams. Now we have a full top-to-bottom integration going, meaning we can work on the stream selection based on actual data, not just made up data.
This commit is contained in:
parent
b351692eea
commit
65220dfc4d
|
@ -51,6 +51,9 @@ import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.schabi.newpipe.App
|
import org.schabi.newpipe.App
|
||||||
import org.schabi.newpipe.MainActivity
|
import org.schabi.newpipe.MainActivity
|
||||||
|
import org.schabi.newpipe.extractor.ServiceList
|
||||||
|
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCStreamExtractor
|
||||||
|
import org.schabi.newpipe.extractor.services.media_ccc.extractors.data.MediaCCCRecording
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
@ -61,7 +64,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(TestMediaRepository())),
|
repository = PrefetchingRepository(CachingRepository(MediaCCCTestRepository())),
|
||||||
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 = …
|
||||||
|
@ -158,3 +161,109 @@ class TestMediaRepository() : MediaRepository {
|
||||||
override suspend fun getTimestampLink(item: String, timestampInSeconds: Long) =
|
override suspend fun getTimestampLink(item: String, timestampInSeconds: Long) =
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MediaCCCTestRepository() : MediaRepository {
|
||||||
|
private val client = OkHttpClient()
|
||||||
|
|
||||||
|
private val service = ServiceList.MediaCCC
|
||||||
|
|
||||||
|
suspend fun fetchPage(item: String): MediaCCCStreamExtractor {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
// TODO: handle MediaCCCLiveStreamExtractor as well
|
||||||
|
val extractor = service.getStreamExtractor(item) as MediaCCCStreamExtractor
|
||||||
|
extractor.fetchPage()
|
||||||
|
extractor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRepoInfo() =
|
||||||
|
MediaRepository.RepoMetaInfo(canHandleTimestampedLinks = true, pullsDataFromNetwork = true)
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
override suspend fun getMetaInfo(item: String): MediaMetadata {
|
||||||
|
// parse as url
|
||||||
|
val extractor = fetchPage(item)
|
||||||
|
return MediaMetadata.Builder().apply {
|
||||||
|
setTitle(extractor.name)
|
||||||
|
setArtist(extractor.subChannelName)
|
||||||
|
setDurationMs(
|
||||||
|
1871L * 1000L
|
||||||
|
)
|
||||||
|
extractor.thumbnails.firstOrNull()?.url?.let {
|
||||||
|
setArtworkUri(Uri.parse(it))
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getStreams(item: String): List<Stream> {
|
||||||
|
val extractor = fetchPage(item)
|
||||||
|
return extractor.recordings.filterIsInstance<MediaCCCRecording.Video>()
|
||||||
|
.filter { it.recordingType == MediaCCCRecording.VideoType.MAIN }
|
||||||
|
.map { track ->
|
||||||
|
Stream(
|
||||||
|
item = item,
|
||||||
|
streamUri = Uri.parse(track.url),
|
||||||
|
streamTracks =
|
||||||
|
listOf(
|
||||||
|
VideoStreamTrack(
|
||||||
|
width = track.width,
|
||||||
|
height = track.height,
|
||||||
|
fileFormat = track.mimeType
|
||||||
|
),
|
||||||
|
) +
|
||||||
|
// one audio track per language
|
||||||
|
// (TODO: probably don’t need to attach the audio track here?)
|
||||||
|
track.languages.map { language ->
|
||||||
|
AudioStreamTrack(
|
||||||
|
// TODO: should we pass the Locale instead??
|
||||||
|
language = language.language,
|
||||||
|
fileFormat = track.mimeType,
|
||||||
|
// TODO: that’s something ExoPlayer should determine for us,
|
||||||
|
// we don’t know that from the metadata
|
||||||
|
bitrate = 44100,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getSubtitles(item: String) =
|
||||||
|
emptyList<Subtitle>()
|
||||||
|
|
||||||
|
override suspend fun getPreviewThumbnail(item: String, timestampInMs: Long): Bitmap? {
|
||||||
|
val extractor = fetchPage(item)
|
||||||
|
|
||||||
|
val templateUrl = extractor.thumbnails.firstOrNull()?.url ?: return null
|
||||||
|
|
||||||
|
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(1, 0)
|
||||||
|
|
||||||
|
override suspend fun getChapters(item: String) =
|
||||||
|
listOf<Chapter>()
|
||||||
|
|
||||||
|
override suspend fun getTimestampLink(item: String, timestampInSeconds: Long) =
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
|
@ -1182,7 +1182,7 @@ public final class VideoDetailFragment
|
||||||
|
|
||||||
final PlayQueue queue = setupPlayQueueForIntent(false);
|
final PlayQueue queue = setupPlayQueueForIntent(false);
|
||||||
tryAddVideoPlayerView();
|
tryAddVideoPlayerView();
|
||||||
newPlayer.playStream("bgp", PlayMode.EMBEDDED_VIDEO);
|
newPlayer.playStream("https://media.ccc.de/v/34c3-9072-bgp_and_the_rule_of_custom", PlayMode.EMBEDDED_VIDEO);
|
||||||
newPlayer.setPlayWhenReady(true);
|
newPlayer.setPlayWhenReady(true);
|
||||||
newPlayer.prepare();
|
newPlayer.prepare();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue