I am developing an Android app for TV using the Leanback library. I have an HLS video stream with an srt subtitle from a URI. I am using ExoPlayer version 2.5.4 as used in this example app. I created my MediaSource using:
private MediaSource onCreateMediaSource(Uri uri, Uri subtitleUri) {
String userAgent = Util.getUserAgent(mContext, "ExoPlayerAdapter");
MediaSource videoSource = new HlsMediaSource(uri,
new DefaultDataSourceFactory(mContext, userAgent),
null,
null);
Format subtitleFormat = Format.createTextSampleFormat(
"1", MimeTypes.APPLICATION_SUBRIP, 0, "en");
MediaSource subtitleSource = new SingleSampleMediaSource(
subtitleUri,
new DefaultDataSourceFactory(mContext, userAgent),
subtitleFormat, C.TIME_UNSET);
MergingMediaSource mergedSource =
new MergingMediaSource(videoSource, subtitleSource);
return mergedSource;
}
In my PlaybackTransportControlGlue, I have a PlaybackControlsRow.ClosedCaptioningAction. When this button is clicked, what do I write in the action dispatcher to toggle the closed captions?
I tried this:
#Override
public void onActionClicked(Action action) {
if (action == mClosedCaptioningAction) {
DefaultTrackSelector trackSelector = mAdapter.getTrackSelector();
int rendererIndex = 0;
if (mClosedCaptioningAction.getIndex() == PlaybackControlsRow.ClosedCaptioningAction.INDEX_ON) {
MappingTrackSelector.MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
TrackGroupArray textGroups = mappedTrackInfo.getTrackGroups(rendererIndex);
int groupIndex = 0;
trackSelector.setRendererDisabled(rendererIndex, false);
MappingTrackSelector.SelectionOverride override =
new MappingTrackSelector.SelectionOverride(mTrackFactory, groupIndex, 0);
trackSelector.setSelectionOverride(rendererIndex, textGroups, override);
} else {
trackSelector.setRendererDisabled(rendererIndex, true);
trackSelector.clearSelectionOverrides();
}
}
super.onActionClicked(action);
}
I got this error:
E/gralloc: unregister FBM buffer
Okay I just answered a question which got text tracks working in a simple way here. This works for adaptive files (like HLS), but I would have to modify it to get it working with progressive files (like an .mp4 that you've merged with an .srt file).
It's at least a starting point.
I'd like to circle back around and get it fully working for you, but I think it may be a matter of working with the track index and tweaking the override so that it doesn't use the AdaptiveFactory (from the below line).
TrackSelection.Factory factory = tracks.length == 1
? new FixedTrackSelection.Factory()
: new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
We have it fully working in our code for both HLS and progressive, but our implementation is wrapped in a lot of additional architecture which might make it even harder to understand the core components.
Related
I am using setMaxBitrate provided by DefaultTrackSelector to set max bit rate when user changes video quality.
val parameters = defaultTrackSelector.buildUponParameters()
.setMaxVideoBitrate(bitrate)
.build()
defaultTrackSelector.parameters = parameters
But as soon as this function is called, the current buffer is discarded & re-buffering is shown right away. Is there any way to keep playing using old buffer & just load the new buffer using the new bitrate settings like YouTube does?
This issue has been discussed here:
https://github.com/google/ExoPlayer/issues/3522
https://github.com/google/ExoPlayer/issues/2250
But there doesn't seem to be any solution yet. Any help regarding this issue would be appreciated. Thanks in advance.
Easily you can do it.You have to use ExoPlayer already and ExoPlayer is provide a seekTo() method.
On this method,You should pass only player current position at which point you stopped before.
Step:-1
You have to change your Quality like 144p to 720p. on this Changing time you have to store your current ExoPlayer current position used this method:-
Private int currentPosition=player.getCurrentPosition();
Step -2
After you have to build your exoplayer media source:-
// Measures bandwidth during playback. Can be null if not required.
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
// Produces DataSource instances through which media data is loaded.
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(this, getString(R.string.app_name)), bandwidthMeter);
// This is the MediaSource representing the media to be played.
MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource("Pass Your Video Url");
// Prepare the player with the source.
player.prepare(videoSource);
Step 3:-
check this condition
if (this.currentPosition > 0) {
player.seekTo(this.currentPosition);
this.currentPosition = 0;
player.setPlayWhenReady(true);
} else {
player.setPlayWhenReady(true);
}
and it's work good you have to watch your video in where are you left.
Step 4:-
If your quality his not good that time used is method.
public int getWifiLevel()
{
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
int linkSpeed = wifiManager.getConnectionInfo().getRssi();
int level = WifiManager.calculateSignalLevel(linkSpeed, 5);
return level;
}
Based on wifi level or link speed you can decide if it has the low connection or high connection internet.
Currently, I have a server that streams four RTMP MediaSources, one with 720p video source, one with 360p video source, one with 180p video source, and one audio-only source. If I wanted to switch resolutions, I have to stop the ExoPlayer instance, prepare the other track I wanted to switch to, then play.
The code I use to prepare the ExoPlayer instance:
TrackSelection.Factory adaptiveTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector = new DefaultTrackSelector(adaptiveTrackSelectionFactory);
RtmpDataSourceFactory rtmpDataSourceFactory = new RtmpDataSourceFactory(bandwidthMeter);
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
factory = new AVControlExtractorMediaSource.Factory(rtmpDataSourceFactory);
factory.setExtractorsFactory(extractorsFactory);
createSource();
//noinspection deprecation
mPlayer = ExoPlayerFactory.newSimpleInstance(mActivity, trackSelector, new DefaultLoadControl(
new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
1000, // min buffer
2000, // max buffer
1000, // playback
1000, //playback after rebuffer
DefaultLoadControl.DEFAULT_TARGET_BUFFER_BYTES,
true
));
vwExoPlayer.setPlayer(mPlayer);
mPlayer.addAnalyticsListener(mAnalyticsListener);
With createSource() being:
private void createSource() {
factory.setTrackPlaybackFlag(AVControlExtractorMediaSource.PLAYBACK_BOTH_AV);
mMediaSource180 = factory.createMediaSource(Uri.parse(API.GAME_VIDEO_STREAM_URL_180()));
mMediaSource180.addEventListener(getHandler(), new MSourceDebuggerListener("GameMediaSource180"));
mMediaSource360 = factory.createMediaSource(Uri.parse(API.GAME_VIDEO_STREAM_URL_360()));
mMediaSource360.addEventListener(getHandler(), new MSourceDebuggerListener("GameMediaSource360"));
mMediaSource720 = factory.createMediaSource(Uri.parse(API.GAME_VIDEO_STREAM_URL_720()));
mMediaSource720.addEventListener(getHandler(), new MSourceDebuggerListener("GameMediaSource720"));
factory.setTrackPlaybackFlag(AVControlExtractorMediaSource.PLAYBACK_AUDIO_ONLY);
mMediaSourceAudio = factory.createMediaSource(Uri.parse(API.GAME_AUDIO_STREAM_URL()));
mMediaSourceAudio.addEventListener(getHandler(), new MSourceDebuggerListener("GameMediaSourceAudio"));
}
private void releaseSource() {
mMediaSource180.releaseSource(null);
mMediaSource360.releaseSource(null);
mMediaSource720.releaseSource(null);
mMediaSourceAudio.releaseSource(null);
}
And the code I currently use to switch between these MediaSources is:
private void changeTrack(MediaSource source) {
if (currentMediaSource == source) return;
try {
this.currentMediaSource = source;
mPlayer.stop(true);
mPlayer.prepare(source, true, true);
mPlayer.setPlayWhenReady(true);
if (source == mMediaSourceAudio) {
if (!audioOnly) {
try {
TransitionManager.beginDelayedTransition(rootView);
} catch (Exception ignored) {
}
layAudioOnly.setVisibility(View.VISIBLE);
vwExoPlayer.setVisibility(View.INVISIBLE);
audioOnly = true;
try {
GameQnAFragment fragment = findFragment(GameQnAFragment.class);
if (fragment != null) {
fragment.signAudioOnly();
}
} catch (Exception e) {
Trace.e(e);
}
try {
GamePollingFragment fragment = findFragment(GamePollingFragment.class);
if (fragment != null) {
fragment.signAudioOnly();
}
} catch (Exception e) {
Trace.e(e);
}
}
} else {
if (audioOnly) {
TransitionManager.beginDelayedTransition(rootView);
layAudioOnly.setVisibility(View.GONE);
vwExoPlayer.setVisibility(View.VISIBLE);
audioOnly = false;
}
}
} catch (Exception ignore) {
}
}
I wanted to implement a seamless switching between these MediaSources so that I don't need to stop and re-prepare, but it appears that this feature is not supported by ExoPlayer.
In addition, logging each MediaSource structure with the following code:
MappingTrackSelector.MappedTrackInfo info = ((DefaultTrackSelector)trackSelector).getCurrentMappedTrackInfo();
if(info != null) {
for (int i = 0; i < info.getRendererCount(); i++) {
TrackGroupArray trackGroups = info.getTrackGroups(i);
if (trackGroups.length != 0) {
for(int j = 0; j < trackGroups.length; j++) {
TrackGroup tg = trackGroups.get(j);
for(int k = 0; k < tg.length; k++) {
Log.i("track_info_"+i+"-"+j+"-"+k, tg.getFormat(k)+"");
}
}
}
}
}
Just nets me 1 video format and 1 audio format each.
My current workaround is to prepare another ExoPlayer instance in the background, replace the currently running instance with that upon preparations being complete, and release the old instance. That reduces the lag between the MediaSources somewhat, but doesn't come close to achieving seamless resolution changes like Youtube.
Should I implement my own TrackSelector and jam-pack all the 4 sources into that, should I implement another MediaSource that handles all 4 sources, or should I just tell the colleague who maintains the streams to switch to just one RTMP MediaSource with a sort of manifest that lists all the resolutions available for the AdaptiveTrackSelection to switch between them?
Adaptive Bit Rate Streaming is designed to allow easy switching between different bit rate streams, but it requires the streams to be segmented and the player to download the video segment by segment.
In this way the player can decide which bit rate to choose for the next segment depending on the current network conditions (and the device display size and t type). The player is able to seamlessly, apart from the different bitrate and quality, move from one bit rate to another this way.
See here for some more info: https://stackoverflow.com/a/42365034/334402
All the above relies on a delivery protocol which supports this segmentation and different bit rate streams. The most common ones today are HLS and MPEG-DASH.
The easiest way to support what I think you are looking for would be for you colleague who is supplying the stream to supply it using HLS and/or DASH.
Note that at the moment, both HLS and DASH are required as apple devices require HLS while other devices tend to default to DASH. Traditionally HLS used TS as the container for the video in the segments and DASH used fragmented MP4, but there is now a move for both to use CMAF, which is essentially fragmented MP4.
So in theory a single set of bit rate videos can be used for HLS and DASH now - in practice this will depend on whether your content is encrypted or not, as HLS and apple used one encryption mode and everyone else another in the past. This is changing now also but will take time before all devices support the new approach, where all devices can support the same encryption mode, so if your streams are encrypted this is an added complication at the moment.
So I am trying to play a DASH video in a SimpleExoplayerView. I am following the most basic tutorials from https://codelabs.developers.google.com/codelabs/exoplayer-intro/#0.
The trouble is, I am able to hear the audio but the screen is always white.
Things I have already tried -
Using three different .mpd links. Can hear the audio each time and the video is white in each case. So there is no problem with the .mpd links.
Tested in different devices - an API 19 HTC Desire and an API 24 Moto G4. So I'm sure it is not a device specific issue.
Here is the initialising method -
private void initialisePlayer() {
TrackSelection.Factory adaptiveTrackSelectionFactory = new AdaptiveTrackSelection.Factory
(defaultBandwidthMeter);
if (exoPlayer == null) {
exoPlayer = ExoPlayerFactory.newSimpleInstance(new DefaultRenderersFactory(context), new
DefaultTrackSelector(adaptiveTrackSelectionFactory), new DefaultLoadControl());
simpleExoPlayerView.setPlayer(exoPlayer);
exoPlayer.setPlayWhenReady(true);
DashMediaSource mediaSource = buildMediaResource();
exoPlayer.prepare(mediaSource);
}
}
And this is the buildMediaSource method -
private DashMediaSource buildMediaResource() {
String userAgent = Util.getUserAgent(context, getString(R.string.app_name));
DataSource.Factory manifestDataSourceFactory = new DefaultHttpDataSourceFactory(userAgent);
DashChunkSource.Factory dashChunkSourceFactory = new DefaultDashChunkSource.Factory(new
DefaultHttpDataSourceFactory(userAgent, defaultBandwidthMeter));
return new DashMediaSource.Factory(dashChunkSourceFactory, manifestDataSourceFactory).createMediaSource(Uri
.parse(url));
}
Am I missing something?
I'm trying to set video bitrate in Exoplayer. I had already set it like this:
trackSelector = new DefaultTrackSelector(factory);
DefaultTrackSelector.Parameters parameters = trackSelector.getParameters();
parameters.withMaxVideoBitrate(maxBitrate);
parameters.withExceedRendererCapabilitiesIfNecessary(false);
parameters.withExceedVideoConstraintsIfNecessary(false);
trackSelector.setParameters(parameters);
but it doesn't work. Everywhere I found something about this I've found people were using HlsChunk source which is private in Exoplayer 2.6. Can anyone help me, pls?
For those who need to set HLS quality according to their needs this is how it could be made, considering that about this topic there are several post on SO but no one is very clear.
As I write in 2019 I assume everyone is using ExoPlayer2.
This is the solution which gave us the best result.
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(Objects.requireNonNull(getContext()),
Util.getUserAgent(this.getContext(), getResources().getString(R.string.app_name)));
trackSelector = new CustomTrackSelector();
videoSource = new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mp4VideoUri);
player = ExoPlayerFactory.newSimpleInstance(this.getContext(), trackSelector);
so what you should is just override the behavior of the custom track selector, overriding the selectTrack method
public class CustomTrackSelector extends DefaultTrackSelector
{
public CustomTrackSelector()
{
super();
}
protected #Nullable
TrackSelection selectVideoTrack(
TrackGroupArray groups,
int[][] formatSupports,
int mixedMimeTypeAdaptationSupports,
Parameters params,
#Nullable TrackSelection.Factory adaptiveTrackSelectionFactory)
throws ExoPlaybackException
{
AdaptiveTrackSelection adaptiveTrackSelection = null;
if (groups.length > 0)
{
for (int groupIndex = 0; groupIndex < groups.length; groupIndex++)
{
TrackGroup trackGroup = groups.get(groupIndex);
int[] tracks = new int[trackGroup.length];
//creation of indexes array
for (int i = 0; i < trackGroup.length; i++)
{
tracks[i] = i;
}
adaptiveTrackSelection = new AdaptiveTrackSelection(
trackGroup,
tracks,
new DefaultBandwidthMeter(),
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
AdaptiveTrackSelection.DEFAULT_BANDWIDTH_FRACTION,
AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
AdaptiveTrackSelection.DEFAULT_MIN_TIME_BETWEEN_BUFFER_REEVALUTATION_MS,
Clock.DEFAULT);
for (int i = 0; i < tracks.length; i++)
{
Format format = trackGroup.getFormat(tracks[i]);
if (format.width < MIN_WIDTH)
{
Logger.log(this, "Video track blacklisted with width = " + format.width);
adaptiveTrackSelection.blacklist(tracks[i], BLACKLIST_DURATION);
} else
{
Logger.log(this, "Video track NOT blacklisted with width = " + format.width);
}
}
}
}
return adaptiveTrackSelection;
}
}
The above method just blacklist the track that you don't want to select, allowing the player to choose just between those that are not blacklisted.
We have blacklisted tracks according to width parameter, but obviously you can filter them using bitRate.
With this behavior the player will start with the track you allow it to use, and after a period of time (BLACKLIST TIME) it can switch back to use all the tracks in case of need.
If you want to exclude a track for all the time just use Integer.MAX_VALUE as blacklist time.
I hope that this will help who is searching for this feature.
Issue description
I'm trying to implement Exoplayer to play background music in loop and speech sounds at the same time that are played on click events.
I can't add silence at the end of the file because I play them in raw to make a sentence like :
file one : "select"
file two : "a category"
That's why I can't have a silence gap between 2 files and there are about 1500 files in the app so I do that to save space.
At first I was with MediaPlayer but figured out that on certain phone (One plus A0001 ) I can't have 2 Mediaplayers working at the same time so I decided to go with Exoplayer.
The 2 players are running in a service.
Background music are ogg files in the raw folder and works great.
I couldn't manage to play mp3 speech file from raw folder so i put them in asset folder and I manage to play them.
The problem I am facing now is that at the end of every mp3 files is cut on some of my devices. The length of the cut depends of the device.
Did I made something wrong ?
Thanks for your help
This is my code for the soundPlayer
DefaultRenderersFactory renderersFactorySound = new DefaultRenderersFactory(this,null, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
exoPlayerSound = ExoPlayerFactory.newSimpleInstance(renderersFactorySound, new DefaultTrackSelector(), new DefaultLoadControl());
exoPlayerSound.addListener(playerSoundEventListener);
exoPlayerSound.setVolume(1.0f);
exoPlayerSound.setRepeatMode(Player.REPEAT_MODE_OFF);
final AssetDataSource dataSource = new AssetDataSource(this);
DataSpec dataSpec = new DataSpec(Uri.parse("asset:///sounds/" + soundsToPlay.get(0) + ".mp3"));
try {
dataSource.open(dataSpec);
} catch (AssetDataSource.AssetDataSourceException e) {
e.printStackTrace();
}
DataSource.Factory factoryMusic = new DataSource.Factory() {
#Override
public DataSource createDataSource() {
return dataSource;
}
};
MediaSource audioSource = new ExtractorMediaSource(Uri.parse("asset:///sounds/" + soundsToPlay.get(0) + ".mp3"), factoryMusic, Mp3Extractor.FACTORY, null, null);
exoPlayerSound.prepare(audioSource);
exoPlayerSound.setPlayWhenReady(true);
This is my code for the musicPlayer
DefaultRenderersFactory renderersFactoryMusic = new DefaultRenderersFactory(this, null, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
playerMusic = ExoPlayerFactory.newSimpleInstance(renderersFactoryMusic, new DefaultTrackSelector(), new DefaultLoadControl());
playerMusic.setVolume(0.4f);
playerMusic.setRepeatMode(Player.REPEAT_MODE_ONE);
DataSpec dataSpecMusic = new DataSpec(RawResourceDataSource.buildRawResourceUri(songId));
final RawResourceDataSource rawResourceDataSourceMusic = new RawResourceDataSource(this);
try {
rawResourceDataSourceMusic.open(dataSpecMusic);
} catch (RawResourceDataSource.RawResourceDataSourceException e) {
e.printStackTrace();
}
DataSource.Factory factoryMusic = new DataSource.Factory() {
#Override
public DataSource createDataSource() {
return rawResourceDataSourceMusic;
}
};
MediaSource audioSourceMusic = new ExtractorMediaSource(rawResourceDataSourceMusic.getUri(), factoryMusic, OggExtractor.FACTORY, null, null);
playerMusic.prepare(audioSourceMusic);
playerMusic.setPlayWhenReady(true);
Version of ExoPlayer being used
compile 'com.google.android.exoplayer:exoplayer-core:r2.5.4'
Device(s) and version(s) of Android being used
Devices with problem :
Samung Galaxy note 3 SM-N9005 (Android v5.0)
Acer Iconia B1-710 (Android v4.1.2)
Device without problem :
One plus A0001 (Android v6.0.1)
Samung Galaxy note 1 GT-N7000 (Android v4.1.2)