I have HLS stream. When I trying insert VAST tag into stream :
exoplayer not show AD with any errors and warnings in logcat
exoplayer throwing exception, and after re-init with HlsMediaSource.Factory(..) works well
How i build ExoPlayer instance:
private fun initPlayer() {
adsLoader = ImaAdsLoader.Builder(requireContext())
.setAdEventListener { adEvent ->
// SOME LISTENER
}
.build()
val httpDataSource = DefaultHttpDataSource.Factory().createDataSource().apply {
setRequestProperty("Referer", "*some referer*")
}
// Set up the factory for media sources, passing the ads loader and ad view providers.
val httpDataSourceFactory = DataSource.Factory { httpDataSource }
val mediaSourceFactory: MediaSource.Factory =
DefaultMediaSourceFactory(httpDataSourceFactory)
.setLocalAdInsertionComponents(
{ unusedAdTagUri: MediaItem.AdsConfiguration? -> adsLoader }
) { //some view for ad
binding.unsafe.adView
}
// some buffering settings
val loadControlBuilder = DefaultLoadControl.Builder()
loadControlBuilder.setBufferDurationsMs(
AppConstants.Player.PLAYER_BUFFER_DURATION,
AppConstants.Player.PLAYER_BUFFER_DURATION,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
)
// init player
playerInstance =
ExoPlayer.Builder(
requireContext(),
DefaultRenderersFactory(requireContext()),
mediaSourceFactory,
DefaultTrackSelector(requireContext()),
loadControlBuilder.build(),
DefaultBandwidthMeter.Builder(requireContext()).build(),
DefaultAnalyticsCollector(Clock.DEFAULT)
).setSeekBackIncrementMs(AppConstants.Player.PLAYER_SEEK_FORWARD)
.setSeekForwardIncrementMs(AppConstants.Player.PLAYER_SEEK_FORWARD)
.build()
}
How i setup my player for HLS stream:
private fun setupExoPlayer(source: String, adLink: String? = null) {
if (playerInstance == null) initPlayer()
binding {
player.playerView.player = playerInstance
adsLoader?.setPlayer(playerInstance)
player.playerView.subtitleView?.visibility = View.GONE
}
val adsConfig = adLink?.let { MediaItem.AdsConfiguration.Builder(Uri.parse(adLink)).build() }
val mediaItem = MediaItem.Builder()
.setUri(source)
.setAdsConfiguration(adsConfig)
.build()
val dataSourceFactory: DataSource.Factory = DefaultHttpDataSource.Factory()
val hlsMediaSource: HlsMediaSource = HlsMediaSource.Factory(dataSourceFactory)
.createMediaSource(mediaItem)
playerInstance?.apply {
setMediaSource(hlsMediaSource)
prepare()
addListener(playerErrorHandler) // adding some listeners here
playWhenReady = true
}
}
How i insert AD VAST tag after some playing time, difference in setMediaSource or setMediaItem of this source:
private fun injectAdIntoExoPlayer(adLink: String) {
val adsConfig = MediaItem.AdsConfiguration.Builder(Uri.parse(adLink)).build()
val mediaItemWithAd = playerInstance?.currentMediaItem?.buildUpon()?.setAdsConfiguration(
adsConfig
)?.build()
mediaItemWithAd?.let {
val dataSourceFactory: DataSource.Factory = DefaultHttpDataSource.Factory()
val hlsMediaSource: HlsMediaSource = HlsMediaSource.Factory(dataSourceFactory)
.createMediaSource(mediaItemWithAd)
// - first case behavior (not showing ad !!!)
playerInstance?.setMediaSource(hlsMediaSource)
// - second case behavior (showing ad !!!)
// playerInstance?.setMediaItem(hlsMediaSource.mediaItem)
}
}
In second case behaviour exoplayer throws this. Same exceptions I have when init player without HlsMediaSource factory:
E/LoadTask: Unexpected exception loading stream
java.lang.ArrayIndexOutOfBoundsException: size=9400 offset=0 byteCount=-940
at com.android.okhttp.okio.Util.checkOffsetAndCount(Util.java:30)
at com.android.okhttp.okio.RealBufferedSource$1.read(RealBufferedSource.java:368)
at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.readInternal(DefaultHttpDataSource.java:768)
at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.read(DefaultHttpDataSource.java:478)
at com.google.android.exoplayer2.upstream.StatsDataSource.read(StatsDataSource.java:92)
at com.google.android.exoplayer2.extractor.DefaultExtractorInput.readFromUpstream(DefaultExtractorInput.java:291)
at com.google.android.exoplayer2.extractor.DefaultExtractorInput.read(DefaultExtractorInput.java:68)
at com.google.android.exoplayer2.extractor.ts.TsExtractor.fillBufferWithAtLeastOnePacket(TsExtractor.java:433)
at com.google.android.exoplayer2.extractor.ts.TsExtractor.read(TsExtractor.java:323)
at com.google.android.exoplayer2.source.hls.BundledHlsMediaChunkExtractor.read(BundledHlsMediaChunkExtractor.java:67)
at com.google.android.exoplayer2.source.hls.HlsMediaChunk.feedDataToExtractor(HlsMediaChunk.java:473)
at com.google.android.exoplayer2.source.hls.HlsMediaChunk.loadMedia(HlsMediaChunk.java:437)
at com.google.android.exoplayer2.source.hls.HlsMediaChunk.load(HlsMediaChunk.java:394)
at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:412)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
E/LoadTask: Unexpected exception loading stream
java.lang.ArrayIndexOutOfBoundsException: size=8192 offset=0 byteCount=-33
at com.android.okhttp.okio.Util.checkOffsetAndCount(Util.java:30)
at com.android.okhttp.okio.RealBufferedSource$1.read(RealBufferedSource.java:368)
at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.readInternal(DefaultHttpDataSource.java:768)
at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.read(DefaultHttpDataSource.java:478)
at com.google.android.exoplayer2.upstream.StatsDataSource.read(StatsDataSource.java:92)
at com.google.android.exoplayer2.upstream.DataSourceInputStream.read(DataSourceInputStream.java:80)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:288)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:351)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:180)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:172)
at java.io.BufferedReader.read(BufferedReader.java:193)
at com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser.checkPlaylistHeader(HlsPlaylistParser.java:296)
at com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser.parse(HlsPlaylistParser.java:259)
at com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser.parse(HlsPlaylistParser.java:68)
at com.google.android.exoplayer2.upstream.ParsingLoadable.load(ParsingLoadable.java:176)
at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:412)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
So i need inject my AD Vast tag with exoplayer:extension-ima dependency without handling player error and reiniting with setMediaSource method and without my AD link
Related
Im migrating a Video screen from XML to Compose., I need to display Ads with the IMA plugin.
Right now, I'm using this dependencies:
implementation "androidx.media3:media3-ui:1.0.0-beta02"
implementation 'androidx.media3:media3-exoplayer:1.0.0-beta02'
implementation "androidx.media3:media3-exoplayer-ima:1.0.0-beta02"
And im implementing the player as follows:
#Composable
fun VideoPlayer(videoUri: String) {
val context = LocalContext.current
val exoPlayer = remember {
ExoPlayer.Builder(context)
.build()
.apply {
val defaultDataSourceFactory = DefaultDataSource.Factory(context)
val dataSourceFactory: DataSource.Factory = DefaultDataSource.Factory(
context,
defaultDataSourceFactory
)
val source = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(videoUri))
setMediaSource(source)
prepare()
}
}
DisposableEffect(
AndroidView(factory = {
PlayerView(context).apply {
hideController()
player = exoPlayer
}
})
) {
onDispose { exoPlayer.release() }
}
}
But, when I run my app and navigate to the ExoPlayer screen, I get this error:
java.lang.ClassCastException: com.google.android.exoplayer2.ui.DefaultTimeBar cannot be cast to androidx.media3.ui.TimeBar
I find this odd, but perhaps in ExoPlayer3 you can customize the TimeBar and choose what class to use. Has someone has faced this before?
I've followed the instruction from Google on how to cast media metadata to chromecast, the initial loading is fine, it will show the title, image and play the stream, but my problem is that I am streaming a live audio stream and need to update the metadata from time to time without having to buffer the audio again.
This is a sample of my code:
override fun loadMediaLoadRequestData(request: PlatformBridgeApis.MediaLoadRequestData?)
{
if (request == null) return
val remoteMediaClient: RemoteMediaClient = remoteMediaClient ?: return
val mediaLoadRequest = getMediaLoadRequestData(request)
remoteMediaClient.load(mediaLoadRequest)
}
fun getMediaLoadRequestData(request: PlatformBridgeApis.MediaLoadRequestData): MediaLoadRequestData {
val mediaInfo = getMediaInfo(request.mediaInfo)
return MediaLoadRequestData.Builder()
.setMediaInfo(mediaInfo)
.setAutoplay(request.shouldAutoplay)
.setCurrentTime(request.currentTime)
.build()
}
fun getMediaInfo(mediaInfo: PlatformBridgeApis.MediaInfo?): MediaInfo? {
if (mediaInfo == null) return null
val streamType = getStreamType(mediaInfo.streamType)
val metadata = getMediaMetadata(mediaInfo.mediaMetadata)
val mediaTracks = mediaInfo.mediaTracks.map { getMediaTrack(it) }
val customData = JSONObject(mediaInfo.customDataAsJson ?: "{}")
return MediaInfo.Builder(mediaInfo.contentId)
.setStreamType(streamType)
.setContentType(mediaInfo.contentType)
.setMetadata(metadata)
.setMediaTracks(mediaTracks)
.setStreamDuration(mediaInfo.streamDuration)
.setCustomData(customData)
.build()
}
Does anyone have any suggestion on how to modify loadMediaLoadRequestData in order to trigger the Chromecast receiver to update only the MediaMetadata and not have the stream buffer again?
Hi I want to display video in exo player . on link I have basic authorize a link look like : http://login:pass#xxx.xxx.x.xxx:port/...
this is how I init a exo player :
private fun initializePlayer() {
if (player == null) {
val trackSelector = DefaultTrackSelector(this)
trackSelector.setParameters(
trackSelector.buildUponParameters().setMaxVideoSizeSd())
player = SimpleExoPlayer.Builder(this)
.setTrackSelector(trackSelector)
.build()
}
playerView!!.player = player
videoViewModel.videoUrlList.forEach {
val mediaItem = MediaItem.fromUri(videoViewModel.getMedia(it)!!)
player!!.addMediaItem(mediaItem)
}
player!!.playWhenReady = videoViewModel.videoStarted
player!!.seekTo(videoViewModel.currentVideoIndex,
videoViewModel.currentVideoPosition.toLong())
player!!.addListener(this)
player!!.addAnalyticsListener(this)
player!!.prepare()
}
And in log I have Caused by: com.google.android.exoplayer2.upstream.HttpDataSource$InvalidResponseCodeException: Response code: 401
this url works correct in webView
fun getMedia(mediaName: String): Uri? {
return if (URLUtil.isValidUrl(mediaName))
Uri.parse(mediaName)
else
null
}
In my application, I had an infinite animation. Actually, it's a TextSwitcher animation running from left to right. However, whenever I tried to prepare the player, I got a jitter issue with the text's animation. Although it's just a few milliseconds, it caused my texts was like jumping from left to right. Here's the prepare video method:
private fun prepareVideo(uri: Uri): SimpleExoPlayer? {
val simpleExoPlayer = SimpleExoPlayer
.Builder(this) // .setLoadControl(defaultLoadControl)
.build()
val mediaItem = MediaItem.fromUri(uri)
simpleExoPlayer.setMediaItem(mediaItem)
val eventListener: Player.EventListener = object : Player.EventListener {
override fun onPlaybackStateChanged(state: Int) {
if (state == ExoPlayer.STATE_READY) {
playVideo(simpleExoPlayer)
}
}
}
simpleExoPlayer.addListener(eventListener)
simpleExoPlayer.playWhenReady = false
simpleExoPlayer.prepare()
showToast("Prepare video")
return simpleExoPlayer
}
Do you guys have any idea to fix it?
If you thinks problem in this part of code i can recommend you to try initialize your SimpleExoPlayer object in application class and use it's instance all across application.
Try with this code
var exoPlayer: SimpleExoPlayer? = null
private fun setupVideo(uriString: String) {
val appName = R.string.app_name
val trackSelector: TrackSelector = DefaultTrackSelector()
val userAgent = context.let { Util.getUserAgent(it, it.getString(appName)) }
val sourceFactory = DefaultDataSourceFactory(requireContext(), userAgent)
val uri = Uri.parse(uriString)
val mediaSource = ProgressiveMediaSource.Factory(sourceFactory).createMediaSource(uri)
exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector)
exoPlayer?.prepare(mediaSource)
exoPlayer?.playWhenReady = false
pvPlayer.player = exoPlayer
}
After quite some time investigating, I found the issue is creating an ExoPlayer instance.
val simpleExoPlayer = SimpleExoPlayer
.Builder(this) // .setLoadControl(defaultLoadControl)
.build()
Just this simple blook took about 0.1 seconds to finish. As a result, it froze the animation for a short period of time. I decided to create it on splash screen and reused it whenever possible.
I implement exoplayer2 in my app for playing HLS Videos, but sometimes it plays just sound, and doesn't work correctly. What am I supposed to do? I couldn't find why this happens sometimes.
this is the code for initializing player :
fun initPalyer(){
val mainHandler = Handler()
val bandwidthMeter: BandwidthMeter = DefaultBandwidthMeter.Builder(context).build()
bandwidthMeter.addEventListener(mainHandler!!, this)
val trackSelectionFactory: TrackSelection.Factory = AdaptiveTrackSelection.Factory()
trackSelector = DefaultTrackSelector(context, trackSelectionFactory)
val builder = ParametersBuilder(context)
trackSelectorParameters = builder.build()
trackSelector!!.parameters = trackSelectorParameters
var rendersFactory: RenderersFactory = app.buildRenderersFactory(false)
player = SimpleExoPlayer.Builder(
context, renderersFactory
)
.setTrackSelector(trackSelector!!)
.setBandwidthMeter(bandwidthMeter)
//.setLoadControl(loadControl)
.build()
player!!.addListener(this)
loadState()
playerView.player = player!!
}
the code for preparing player :
private fun preparePlayer(uri: Uri) {
val mediaSource = MediaSourceBuilder().build(uri)
durationSet = false
player?.prepare(mediaSource, true, false)
}
and the code for creating mediaSources :
class MediaSourceBuilder {
//Build various MediaSource depending upon the type of Media for a given video/audio uri
fun build(uri: Uri): MediaSource {
val userAgent = PlayerConstants.USER_AGENT
val lastPath = uri.lastPathSegment?:""
val defaultHttpDataSourceFactory = DefaultHttpDataSourceFactory(userAgent)
if(lastPath.contains(PlayerConstants.FORMAT_MP3) || lastPath.contains(PlayerConstants.FORMAT_MP4)){
return ExtractorMediaSource.Factory(defaultHttpDataSourceFactory)
.createMediaSource(uri)
}else if(lastPath.contains(PlayerConstants.FORMAT_M3U8)){
return HlsMediaSource.Factory(defaultHttpDataSourceFactory)
.setAllowChunklessPreparation(true)
.createMediaSource(uri)
}else{
val dashChunkSourceFactory = DefaultDashChunkSource.Factory(defaultHttpDataSourceFactory)
return DashMediaSource.Factory(dashChunkSourceFactory, defaultHttpDataSourceFactory)
.createMediaSource(uri)
}
}
You might wanna change your DataSourceFactory, in case your URL are in HTTPS you might end up with an error, try using DefaultDataSourceFactory instead of DefaultHttpDataSourceFactory