Created
September 29, 2017 12:37
-
-
Save FaisalJamil/fd26256492506b5faed778e155359d8e to your computer and use it in GitHub Desktop.
Customize ExoPlayer with SubTitles
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2014 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.earthlinktele.faisaljamil.cinemana.player; | |
import android.media.MediaCodec.CryptoException; | |
import android.os.Handler; | |
import android.os.Looper; | |
import android.view.Surface; | |
import com.google.android.exoplayer.CodecCounters; | |
import com.google.android.exoplayer.DummyTrackRenderer; | |
import com.google.android.exoplayer.ExoPlaybackException; | |
import com.google.android.exoplayer.ExoPlayer; | |
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; | |
import com.google.android.exoplayer.MediaCodecTrackRenderer; | |
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException; | |
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; | |
import com.google.android.exoplayer.MediaFormat; | |
import com.google.android.exoplayer.TimeRange; | |
import com.google.android.exoplayer.TrackRenderer; | |
import com.google.android.exoplayer.audio.AudioTrack; | |
import com.google.android.exoplayer.chunk.ChunkSampleSource; | |
import com.google.android.exoplayer.chunk.Format; | |
import com.google.android.exoplayer.dash.DashChunkSource; | |
import com.google.android.exoplayer.drm.StreamingDrmSessionManager; | |
import com.google.android.exoplayer.hls.HlsSampleSource; | |
import com.google.android.exoplayer.metadata.MetadataTrackRenderer.MetadataRenderer; | |
import com.google.android.exoplayer.text.Cue; | |
import com.google.android.exoplayer.text.TextRenderer; | |
import com.google.android.exoplayer.upstream.BandwidthMeter; | |
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; | |
import com.google.android.exoplayer.util.DebugTextViewHelper; | |
import com.google.android.exoplayer.util.PlayerControl; | |
import java.io.IOException; | |
import java.util.Collections; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.concurrent.CopyOnWriteArrayList; | |
/** | |
* A wrapper around {@link ExoPlayer} that provides a higher level interface. It can be prepared | |
* with one of a number of {@link RendererBuilder} classes to suit different use cases (e.g. DASH, | |
* SmoothStreaming and so on). | |
*/ | |
public class CinemanaPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventListener, | |
HlsSampleSource.EventListener, DefaultBandwidthMeter.EventListener, | |
MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener, | |
StreamingDrmSessionManager.EventListener, DashChunkSource.EventListener, TextRenderer, | |
MetadataRenderer<Map<String, Object>>, DebugTextViewHelper.Provider { | |
/** | |
* Builds renderers for the player. | |
*/ | |
public interface RendererBuilder { | |
/** | |
* Builds renderers for playback. | |
* | |
* @param player The player for which renderers are being built. {@link CinemanaPlayer#onRenderers} | |
* should be invoked once the renderers have been built. If building fails, | |
* {@link CinemanaPlayer#onRenderersError} should be invoked. | |
*/ | |
void buildRenderers(CinemanaPlayer player); | |
/** | |
* Cancels the current build operation, if there is one. Else does nothing. | |
* <p> | |
* A canceled build operation must not invoke {@link CinemanaPlayer#onRenderers} or | |
* {@link CinemanaPlayer#onRenderersError} on the player, which may have been released. | |
*/ | |
void cancel(); | |
} | |
/** | |
* A listener for core events. | |
*/ | |
public interface Listener { | |
void onStateChanged(boolean playWhenReady, int playbackState); | |
void onError(Exception e); | |
void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, | |
float pixelWidthHeightRatio); | |
} | |
/** | |
* A listener for internal errors. | |
* <p> | |
* These errors are not visible to the user, and hence this listener is provided for | |
* informational purposes only. Note however that an internal error may cause a fatal | |
* error if the player fails to recover. If this happens, {@link Listener#onError(Exception)} | |
* will be invoked. | |
*/ | |
public interface InternalErrorListener { | |
void onRendererInitializationError(Exception e); | |
void onAudioTrackInitializationError(AudioTrack.InitializationException e); | |
void onAudioTrackWriteError(AudioTrack.WriteException e); | |
void onDecoderInitializationError(DecoderInitializationException e); | |
void onCryptoError(CryptoException e); | |
void onLoadError(int sourceId, IOException e); | |
void onDrmSessionManagerError(Exception e); | |
} | |
/** | |
* A listener for debugging information. | |
*/ | |
public interface InfoListener { | |
void onVideoFormatEnabled(Format format, int trigger, long mediaTimeMs); | |
void onAudioFormatEnabled(Format format, int trigger, long mediaTimeMs); | |
void onDroppedFrames(int count, long elapsed); | |
void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate); | |
void onLoadStarted(int sourceId, long length, int type, int trigger, Format format, | |
long mediaStartTimeMs, long mediaEndTimeMs); | |
void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format, | |
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs); | |
void onDecoderInitialized(String decoderName, long elapsedRealtimeMs, | |
long initializationDurationMs); | |
void onAvailableRangeChanged(TimeRange availableRange); | |
} | |
/** | |
* A listener for receiving notifications of timed text. | |
*/ | |
public interface CaptionListener { | |
void onCues(List<Cue> cues); | |
} | |
/** | |
* A listener for receiving ID3 metadata parsed from the media stream. | |
*/ | |
public interface Id3MetadataListener { | |
void onId3Metadata(Map<String, Object> metadata); | |
} | |
// Constants pulled into this class for convenience. | |
public static final int STATE_IDLE = ExoPlayer.STATE_IDLE; | |
public static final int STATE_PREPARING = ExoPlayer.STATE_PREPARING; | |
public static final int STATE_BUFFERING = ExoPlayer.STATE_BUFFERING; | |
public static final int STATE_READY = ExoPlayer.STATE_READY; | |
public static final int STATE_ENDED = ExoPlayer.STATE_ENDED; | |
public static final int TRACK_DISABLED = ExoPlayer.TRACK_DISABLED; | |
public static final int TRACK_DEFAULT = ExoPlayer.TRACK_DEFAULT; | |
public static final int RENDERER_COUNT = 4; | |
public static final int TYPE_VIDEO = 0; | |
public static final int TYPE_AUDIO = 1; | |
public static final int TYPE_TEXT = 2; | |
public static final int TYPE_METADATA = 3; | |
private static final int RENDERER_BUILDING_STATE_IDLE = 1; | |
private static final int RENDERER_BUILDING_STATE_BUILDING = 2; | |
private static final int RENDERER_BUILDING_STATE_BUILT = 3; | |
private final RendererBuilder rendererBuilder; | |
private final ExoPlayer player; | |
private final PlayerControl playerControl; | |
private final Handler mainHandler; | |
private final CopyOnWriteArrayList<Listener> listeners; | |
private int rendererBuildingState; | |
private int lastReportedPlaybackState; | |
private boolean lastReportedPlayWhenReady; | |
private Surface surface; | |
private TrackRenderer videoRenderer; | |
private CodecCounters codecCounters; | |
private Format videoFormat; | |
private int videoTrackToRestore; | |
private BandwidthMeter bandwidthMeter; | |
private boolean backgrounded; | |
private CaptionListener captionListener; | |
private Id3MetadataListener id3MetadataListener; | |
private InternalErrorListener internalErrorListener; | |
private InfoListener infoListener; | |
public CinemanaPlayer(RendererBuilder rendererBuilder) { | |
this.rendererBuilder = rendererBuilder; | |
player = ExoPlayer.Factory.newInstance(RENDERER_COUNT, 1000, 5000); | |
player.addListener(this); | |
playerControl = new PlayerControl(player); | |
mainHandler = new Handler(); | |
listeners = new CopyOnWriteArrayList<>(); | |
lastReportedPlaybackState = STATE_IDLE; | |
rendererBuildingState = RENDERER_BUILDING_STATE_IDLE; | |
// Disable text initially. | |
player.setSelectedTrack(TYPE_TEXT, TRACK_DISABLED); | |
} | |
public PlayerControl getPlayerControl() { | |
return playerControl; | |
} | |
public void addListener(Listener listener) { | |
listeners.add(listener); | |
} | |
public void removeListener(Listener listener) { | |
listeners.remove(listener); | |
} | |
public void setInternalErrorListener(InternalErrorListener listener) { | |
internalErrorListener = listener; | |
} | |
public void setInfoListener(InfoListener listener) { | |
infoListener = listener; | |
} | |
public void setCaptionListener(CaptionListener listener) { | |
captionListener = listener; | |
} | |
public void setMetadataListener(Id3MetadataListener listener) { | |
id3MetadataListener = listener; | |
} | |
public void setSurface(Surface surface) { | |
this.surface = surface; | |
pushSurface(false); | |
} | |
public Surface getSurface() { | |
return surface; | |
} | |
public void blockingClearSurface() { | |
surface = null; | |
pushSurface(true); | |
} | |
public int getTrackCount(int type) { | |
return player.getTrackCount(type); | |
} | |
public MediaFormat getTrackFormat(int type, int index) { | |
return player.getTrackFormat(type, index); | |
} | |
public int getSelectedTrack(int type) { | |
return player.getSelectedTrack(type); | |
} | |
public void setSelectedTrack(int type, int index) { | |
player.setSelectedTrack(type, index); | |
if (type == TYPE_TEXT && index < 0 && captionListener != null) { | |
captionListener.onCues(Collections.<Cue>emptyList()); | |
} | |
} | |
public boolean getBackgrounded() { | |
return backgrounded; | |
} | |
public void setBackgrounded(boolean backgrounded) { | |
if (this.backgrounded == backgrounded) { | |
return; | |
} | |
this.backgrounded = backgrounded; | |
if (backgrounded) { | |
videoTrackToRestore = getSelectedTrack(TYPE_VIDEO); | |
setSelectedTrack(TYPE_VIDEO, TRACK_DISABLED); | |
blockingClearSurface(); | |
} else { | |
setSelectedTrack(TYPE_VIDEO, videoTrackToRestore); | |
} | |
} | |
public void prepare() { | |
if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILT) { | |
player.stop(); | |
} | |
rendererBuilder.cancel(); | |
videoFormat = null; | |
videoRenderer = null; | |
rendererBuildingState = RENDERER_BUILDING_STATE_BUILDING; | |
maybeReportPlayerState(); | |
rendererBuilder.buildRenderers(this); | |
} | |
/** | |
* Invoked with the results from a {@link RendererBuilder}. | |
* | |
* @param renderers Renderers indexed by {@link CinemanaPlayer} TYPE_* constants. An individual | |
* element may be null if there do not exist tracks of the corresponding type. | |
* @param bandwidthMeter Provides an estimate of the currently available bandwidth. May be null. | |
*/ | |
/* package */ void onRenderers(TrackRenderer[] renderers, BandwidthMeter bandwidthMeter) { | |
for (int i = 0; i < RENDERER_COUNT; i++) { | |
if (renderers[i] == null) { | |
// Convert a null renderer to a dummy renderer. | |
renderers[i] = new DummyTrackRenderer(); | |
} | |
} | |
// Complete preparation. | |
this.videoRenderer = renderers[TYPE_VIDEO]; | |
this.codecCounters = videoRenderer instanceof MediaCodecTrackRenderer | |
? ((MediaCodecTrackRenderer) videoRenderer).codecCounters | |
: renderers[TYPE_AUDIO] instanceof MediaCodecTrackRenderer | |
? ((MediaCodecTrackRenderer) renderers[TYPE_AUDIO]).codecCounters : null; | |
this.bandwidthMeter = bandwidthMeter; | |
pushSurface(false); | |
player.prepare(renderers); | |
rendererBuildingState = RENDERER_BUILDING_STATE_BUILT; | |
} | |
/** | |
* Invoked if a {@link RendererBuilder} encounters an error. | |
* | |
* @param e Describes the error. | |
*/ | |
/* package */ void onRenderersError(Exception e) { | |
if (internalErrorListener != null) { | |
internalErrorListener.onRendererInitializationError(e); | |
} | |
for (Listener listener : listeners) { | |
listener.onError(e); | |
} | |
rendererBuildingState = RENDERER_BUILDING_STATE_IDLE; | |
maybeReportPlayerState(); | |
} | |
public void setPlayWhenReady(boolean playWhenReady) { | |
player.setPlayWhenReady(playWhenReady); | |
} | |
public void seekTo(long positionMs) { | |
player.seekTo(positionMs); | |
} | |
public void release() { | |
rendererBuilder.cancel(); | |
rendererBuildingState = RENDERER_BUILDING_STATE_IDLE; | |
surface = null; | |
player.release(); | |
} | |
public int getPlaybackState() { | |
if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILDING) { | |
return STATE_PREPARING; | |
} | |
int playerState = player.getPlaybackState(); | |
if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILT && playerState == STATE_IDLE) { | |
// This is an edge case where the renderers are built, but are still being passed to the | |
// player's playback thread. | |
return STATE_PREPARING; | |
} | |
return playerState; | |
} | |
@Override | |
public Format getFormat() { | |
return videoFormat; | |
} | |
@Override | |
public BandwidthMeter getBandwidthMeter() { | |
return bandwidthMeter; | |
} | |
@Override | |
public CodecCounters getCodecCounters() { | |
return codecCounters; | |
} | |
@Override | |
public long getCurrentPosition() { | |
return player.getCurrentPosition(); | |
} | |
public long getDuration() { | |
return player.getDuration(); | |
} | |
public int getBufferedPercentage() { | |
return player.getBufferedPercentage(); | |
} | |
public boolean getPlayWhenReady() { | |
return player.getPlayWhenReady(); | |
} | |
/* package */ Looper getPlaybackLooper() { | |
return player.getPlaybackLooper(); | |
} | |
/* package */ Handler getMainHandler() { | |
return mainHandler; | |
} | |
@Override | |
public void onPlayerStateChanged(boolean playWhenReady, int state) { | |
maybeReportPlayerState(); | |
} | |
@Override | |
public void onPlayerError(ExoPlaybackException exception) { | |
rendererBuildingState = RENDERER_BUILDING_STATE_IDLE; | |
for (Listener listener : listeners) { | |
listener.onError(exception); | |
} | |
} | |
@Override | |
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, | |
float pixelWidthHeightRatio) { | |
for (Listener listener : listeners) { | |
listener.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio); | |
} | |
} | |
@Override | |
public void onDroppedFrames(int count, long elapsed) { | |
if (infoListener != null) { | |
infoListener.onDroppedFrames(count, elapsed); | |
} | |
} | |
@Override | |
public void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate) { | |
if (infoListener != null) { | |
infoListener.onBandwidthSample(elapsedMs, bytes, bitrateEstimate); | |
} | |
} | |
@Override | |
public void onDownstreamFormatChanged(int sourceId, Format format, int trigger, | |
long mediaTimeMs) { | |
if (infoListener == null) { | |
return; | |
} | |
if (sourceId == TYPE_VIDEO) { | |
videoFormat = format; | |
infoListener.onVideoFormatEnabled(format, trigger, mediaTimeMs); | |
} else if (sourceId == TYPE_AUDIO) { | |
infoListener.onAudioFormatEnabled(format, trigger, mediaTimeMs); | |
} | |
} | |
@Override | |
public void onDrmKeysLoaded() { | |
// Do nothing. | |
} | |
@Override | |
public void onDrmSessionManagerError(Exception e) { | |
if (internalErrorListener != null) { | |
internalErrorListener.onDrmSessionManagerError(e); | |
} | |
} | |
@Override | |
public void onDecoderInitializationError(DecoderInitializationException e) { | |
if (internalErrorListener != null) { | |
internalErrorListener.onDecoderInitializationError(e); | |
} | |
} | |
@Override | |
public void onAudioTrackInitializationError(AudioTrack.InitializationException e) { | |
if (internalErrorListener != null) { | |
internalErrorListener.onAudioTrackInitializationError(e); | |
} | |
} | |
@Override | |
public void onAudioTrackWriteError(AudioTrack.WriteException e) { | |
if (internalErrorListener != null) { | |
internalErrorListener.onAudioTrackWriteError(e); | |
} | |
} | |
@Override | |
public void onCryptoError(CryptoException e) { | |
if (internalErrorListener != null) { | |
internalErrorListener.onCryptoError(e); | |
} | |
} | |
@Override | |
public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs, | |
long initializationDurationMs) { | |
if (infoListener != null) { | |
infoListener.onDecoderInitialized(decoderName, elapsedRealtimeMs, initializationDurationMs); | |
} | |
} | |
@Override | |
public void onLoadError(int sourceId, IOException e) { | |
if (internalErrorListener != null) { | |
internalErrorListener.onLoadError(sourceId, e); | |
} | |
} | |
@Override | |
public void onCues(List<Cue> cues) { | |
if (captionListener != null && getSelectedTrack(TYPE_TEXT) != TRACK_DISABLED) { | |
captionListener.onCues(cues); | |
} | |
} | |
@Override | |
public void onMetadata(Map<String, Object> metadata) { | |
if (id3MetadataListener != null && getSelectedTrack(TYPE_METADATA) != TRACK_DISABLED) { | |
id3MetadataListener.onId3Metadata(metadata); | |
} | |
} | |
@Override | |
public void onAvailableRangeChanged(TimeRange availableRange) { | |
if (infoListener != null) { | |
infoListener.onAvailableRangeChanged(availableRange); | |
} | |
} | |
@Override | |
public void onPlayWhenReadyCommitted() { | |
// Do nothing. | |
} | |
@Override | |
public void onDrawnToSurface(Surface surface) { | |
// Do nothing. | |
} | |
@Override | |
public void onLoadStarted(int sourceId, long length, int type, int trigger, Format format, | |
long mediaStartTimeMs, long mediaEndTimeMs) { | |
if (infoListener != null) { | |
infoListener.onLoadStarted(sourceId, length, type, trigger, format, mediaStartTimeMs, | |
mediaEndTimeMs); | |
} | |
} | |
@Override | |
public void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format, | |
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs) { | |
if (infoListener != null) { | |
infoListener.onLoadCompleted(sourceId, bytesLoaded, type, trigger, format, mediaStartTimeMs, | |
mediaEndTimeMs, elapsedRealtimeMs, loadDurationMs); | |
} | |
} | |
@Override | |
public void onLoadCanceled(int sourceId, long bytesLoaded) { | |
// Do nothing. | |
} | |
@Override | |
public void onUpstreamDiscarded(int sourceId, long mediaStartTimeMs, long mediaEndTimeMs) { | |
// Do nothing. | |
} | |
private void maybeReportPlayerState() { | |
boolean playWhenReady = player.getPlayWhenReady(); | |
int playbackState = getPlaybackState(); | |
if (lastReportedPlayWhenReady != playWhenReady || lastReportedPlaybackState != playbackState) { | |
for (Listener listener : listeners) { | |
listener.onStateChanged(playWhenReady, playbackState); | |
} | |
lastReportedPlayWhenReady = playWhenReady; | |
lastReportedPlaybackState = playbackState; | |
} | |
} | |
private void pushSurface(boolean blockForSurfacePush) { | |
if (videoRenderer == null) { | |
return; | |
} | |
if (blockForSurfacePush) { | |
player.blockingSendMessage( | |
videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface); | |
} else { | |
player.sendMessage( | |
videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2014 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.earthlinktele.faisaljamil.cinemana.player; | |
import android.content.Context; | |
import android.media.MediaCodec; | |
import android.os.Handler; | |
import android.util.Log; | |
import com.earthlinktele.faisaljamil.cinemana.player.CinemanaPlayer.RendererBuilder; | |
import com.google.android.exoplayer.DefaultLoadControl; | |
import com.google.android.exoplayer.LoadControl; | |
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; | |
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; | |
import com.google.android.exoplayer.TrackRenderer; | |
import com.google.android.exoplayer.audio.AudioCapabilities; | |
import com.google.android.exoplayer.chunk.ChunkSampleSource; | |
import com.google.android.exoplayer.chunk.ChunkSource; | |
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator; | |
import com.google.android.exoplayer.dash.DashChunkSource; | |
import com.google.android.exoplayer.dash.DefaultDashTrackSelector; | |
import com.google.android.exoplayer.dash.mpd.AdaptationSet; | |
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription; | |
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser; | |
import com.google.android.exoplayer.dash.mpd.Period; | |
import com.google.android.exoplayer.dash.mpd.UtcTimingElement; | |
import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver; | |
import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver.UtcTimingCallback; | |
import com.google.android.exoplayer.drm.MediaDrmCallback; | |
import com.google.android.exoplayer.drm.StreamingDrmSessionManager; | |
import com.google.android.exoplayer.drm.UnsupportedDrmException; | |
import com.google.android.exoplayer.text.TextTrackRenderer; | |
import com.google.android.exoplayer.upstream.DataSource; | |
import com.google.android.exoplayer.upstream.DefaultAllocator; | |
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; | |
import com.google.android.exoplayer.upstream.DefaultUriDataSource; | |
import com.google.android.exoplayer.upstream.UriDataSource; | |
import com.google.android.exoplayer.util.ManifestFetcher; | |
import com.google.android.exoplayer.util.Util; | |
import java.io.IOException; | |
/** | |
* A {@link RendererBuilder} for DASH. | |
*/ | |
public class DashRendererBuilder implements RendererBuilder { | |
private static final String TAG = "DashRendererBuilder"; | |
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024; | |
private static final int VIDEO_BUFFER_SEGMENTS = 200; | |
private static final int AUDIO_BUFFER_SEGMENTS = 54; | |
private static final int TEXT_BUFFER_SEGMENTS = 2; | |
private static final int LIVE_EDGE_LATENCY_MS = 30000; | |
private static final int SECURITY_LEVEL_UNKNOWN = -1; | |
private static final int SECURITY_LEVEL_1 = 1; | |
private static final int SECURITY_LEVEL_3 = 3; | |
private final Context context; | |
private final String userAgent; | |
private final String url; | |
private final MediaDrmCallback drmCallback; | |
private AsyncRendererBuilder currentAsyncBuilder; | |
public DashRendererBuilder(Context context, String userAgent, String url, | |
MediaDrmCallback drmCallback) { | |
this.context = context; | |
this.userAgent = userAgent; | |
this.url = url; | |
this.drmCallback = drmCallback; | |
} | |
@Override | |
public void buildRenderers(CinemanaPlayer player) { | |
currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, drmCallback, player); | |
currentAsyncBuilder.init(); | |
} | |
@Override | |
public void cancel() { | |
if (currentAsyncBuilder != null) { | |
currentAsyncBuilder.cancel(); | |
currentAsyncBuilder = null; | |
} | |
} | |
private static final class AsyncRendererBuilder | |
implements ManifestFetcher.ManifestCallback<MediaPresentationDescription>, UtcTimingCallback { | |
private final Context context; | |
private final String userAgent; | |
private final MediaDrmCallback drmCallback; | |
private final CinemanaPlayer player; | |
private final ManifestFetcher<MediaPresentationDescription> manifestFetcher; | |
private final UriDataSource manifestDataSource; | |
private boolean canceled; | |
private MediaPresentationDescription manifest; | |
private long elapsedRealtimeOffset; | |
public AsyncRendererBuilder(Context context, String userAgent, String url, | |
MediaDrmCallback drmCallback, CinemanaPlayer player) { | |
this.context = context; | |
this.userAgent = userAgent; | |
this.drmCallback = drmCallback; | |
this.player = player; | |
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser(); | |
manifestDataSource = new DefaultUriDataSource(context, userAgent); | |
manifestFetcher = new ManifestFetcher<>(url, manifestDataSource, parser); | |
} | |
public void init() { | |
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this); | |
} | |
public void cancel() { | |
canceled = true; | |
} | |
@Override | |
public void onSingleManifest(MediaPresentationDescription manifest) { | |
if (canceled) { | |
return; | |
} | |
this.manifest = manifest; | |
if (manifest.dynamic && manifest.utcTiming != null) { | |
UtcTimingElementResolver.resolveTimingElement(manifestDataSource, manifest.utcTiming, | |
manifestFetcher.getManifestLoadCompleteTimestamp(), this); | |
} else { | |
buildRenderers(); | |
} | |
} | |
@Override | |
public void onSingleManifestError(IOException e) { | |
if (canceled) { | |
return; | |
} | |
player.onRenderersError(e); | |
} | |
@Override | |
public void onTimestampResolved(UtcTimingElement utcTiming, long elapsedRealtimeOffset) { | |
if (canceled) { | |
return; | |
} | |
this.elapsedRealtimeOffset = elapsedRealtimeOffset; | |
buildRenderers(); | |
} | |
@Override | |
public void onTimestampError(UtcTimingElement utcTiming, IOException e) { | |
if (canceled) { | |
return; | |
} | |
Log.e(TAG, "Failed to resolve UtcTiming element [" + utcTiming + "]", e); | |
// Be optimistic and continue in the hope that the device clock is correct. | |
buildRenderers(); | |
} | |
private void buildRenderers() { | |
Period period = manifest.getPeriod(0); | |
Handler mainHandler = player.getMainHandler(); | |
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); | |
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player); | |
boolean hasContentProtection = false; | |
for (int i = 0; i < period.adaptationSets.size(); i++) { | |
AdaptationSet adaptationSet = period.adaptationSets.get(i); | |
if (adaptationSet.type != AdaptationSet.TYPE_UNKNOWN) { | |
hasContentProtection |= adaptationSet.hasContentProtection(); | |
} | |
} | |
// Check drm support if necessary. | |
boolean filterHdContent = false; | |
StreamingDrmSessionManager drmSessionManager = null; | |
if (hasContentProtection) { | |
if (Util.SDK_INT < 18) { | |
player.onRenderersError( | |
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME)); | |
return; | |
} | |
try { | |
drmSessionManager = StreamingDrmSessionManager.newWidevineInstance( | |
player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player); | |
filterHdContent = getWidevineSecurityLevel(drmSessionManager) != SECURITY_LEVEL_1; | |
} catch (UnsupportedDrmException e) { | |
player.onRenderersError(e); | |
return; | |
} | |
} | |
// Build the video renderer. | |
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); | |
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, | |
DefaultDashTrackSelector.newVideoInstance(context, true, filterHdContent), | |
videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS, | |
elapsedRealtimeOffset, mainHandler, player); | |
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, | |
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, | |
CinemanaPlayer.TYPE_VIDEO); | |
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, videoSampleSource, | |
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, drmSessionManager, true, | |
mainHandler, player, 50); | |
// Build the audio renderer. | |
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); | |
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, | |
DefaultDashTrackSelector.newAudioInstance(), audioDataSource, null, LIVE_EDGE_LATENCY_MS, | |
elapsedRealtimeOffset, mainHandler, player); | |
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl, | |
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, | |
CinemanaPlayer.TYPE_AUDIO); | |
TrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource, | |
drmSessionManager, true, mainHandler, player, AudioCapabilities.getCapabilities(context)); | |
// Build the text renderer. | |
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); | |
ChunkSource textChunkSource = new DashChunkSource(manifestFetcher, | |
DefaultDashTrackSelector.newTextInstance(), textDataSource, null, LIVE_EDGE_LATENCY_MS, | |
elapsedRealtimeOffset, mainHandler, player); | |
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl, | |
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, | |
CinemanaPlayer.TYPE_TEXT); | |
TrackRenderer textRenderer = new TextTrackRenderer(textSampleSource, player, | |
mainHandler.getLooper()); | |
// Invoke the callback. | |
TrackRenderer[] renderers = new TrackRenderer[CinemanaPlayer.RENDERER_COUNT]; | |
renderers[CinemanaPlayer.TYPE_VIDEO] = videoRenderer; | |
renderers[CinemanaPlayer.TYPE_AUDIO] = audioRenderer; | |
renderers[CinemanaPlayer.TYPE_TEXT] = textRenderer; | |
player.onRenderers(renderers, bandwidthMeter); | |
} | |
private static int getWidevineSecurityLevel(StreamingDrmSessionManager sessionManager) { | |
String securityLevelProperty = sessionManager.getPropertyString("securityLevel"); | |
return securityLevelProperty.equals("L1") ? SECURITY_LEVEL_1 : securityLevelProperty | |
.equals("L3") ? SECURITY_LEVEL_3 : SECURITY_LEVEL_UNKNOWN; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2014 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.earthlinktele.faisaljamil.cinemana.player; | |
import android.media.MediaCodec.CryptoException; | |
import android.os.SystemClock; | |
import android.util.Log; | |
import com.google.android.exoplayer.ExoPlayer; | |
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException; | |
import com.google.android.exoplayer.TimeRange; | |
import com.google.android.exoplayer.audio.AudioTrack; | |
import com.google.android.exoplayer.chunk.Format; | |
import com.google.android.exoplayer.util.VerboseLogUtil; | |
import java.io.IOException; | |
import java.text.NumberFormat; | |
import java.util.Locale; | |
/** | |
* Logs player events using {@link Log}. | |
*/ | |
public class EventLogger implements CinemanaPlayer.Listener, CinemanaPlayer.InfoListener, | |
CinemanaPlayer.InternalErrorListener { | |
private static final String TAG = "EventLogger"; | |
private static final NumberFormat TIME_FORMAT; | |
static { | |
TIME_FORMAT = NumberFormat.getInstance(Locale.US); | |
TIME_FORMAT.setMinimumFractionDigits(2); | |
TIME_FORMAT.setMaximumFractionDigits(2); | |
} | |
private long sessionStartTimeMs; | |
private long[] loadStartTimeMs; | |
private long[] availableRangeValuesUs; | |
public EventLogger() { | |
loadStartTimeMs = new long[CinemanaPlayer.RENDERER_COUNT]; | |
} | |
public void startSession() { | |
sessionStartTimeMs = SystemClock.elapsedRealtime(); | |
Log.d(TAG, "start [0]"); | |
} | |
public void endSession() { | |
Log.d(TAG, "end [" + getSessionTimeString() + "]"); | |
} | |
// DemoPlayer.Listener | |
@Override | |
public void onStateChanged(boolean playWhenReady, int state) { | |
Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", " | |
+ getStateString(state) + "]"); | |
} | |
@Override | |
public void onError(Exception e) { | |
Log.e(TAG, "playerFailed [" + getSessionTimeString() + "]", e); | |
} | |
@Override | |
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, | |
float pixelWidthHeightRatio) { | |
Log.d(TAG, "videoSizeChanged [" + width + ", " + height + ", " + unappliedRotationDegrees | |
+ ", " + pixelWidthHeightRatio + "]"); | |
} | |
// DemoPlayer.InfoListener | |
@Override | |
public void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate) { | |
Log.d(TAG, "bandwidth [" + getSessionTimeString() + ", " + bytes + ", " | |
+ getTimeString(elapsedMs) + ", " + bitrateEstimate + "]"); | |
} | |
@Override | |
public void onDroppedFrames(int count, long elapsed) { | |
Log.d(TAG, "droppedFrames [" + getSessionTimeString() + ", " + count + "]"); | |
} | |
@Override | |
public void onLoadStarted(int sourceId, long length, int type, int trigger, Format format, | |
long mediaStartTimeMs, long mediaEndTimeMs) { | |
loadStartTimeMs[sourceId] = SystemClock.elapsedRealtime(); | |
if (VerboseLogUtil.isTagEnabled(TAG)) { | |
Log.v(TAG, "loadStart [" + getSessionTimeString() + ", " + sourceId + ", " + type | |
+ ", " + mediaStartTimeMs + ", " + mediaEndTimeMs + "]"); | |
} | |
} | |
@Override | |
public void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format, | |
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs) { | |
if (VerboseLogUtil.isTagEnabled(TAG)) { | |
long downloadTime = SystemClock.elapsedRealtime() - loadStartTimeMs[sourceId]; | |
Log.v(TAG, "loadEnd [" + getSessionTimeString() + ", " + sourceId + ", " + downloadTime | |
+ "]"); | |
} | |
} | |
@Override | |
public void onVideoFormatEnabled(Format format, int trigger, long mediaTimeMs) { | |
Log.d(TAG, "videoFormat [" + getSessionTimeString() + ", " + format.id + ", " | |
+ Integer.toString(trigger) + "]"); | |
} | |
@Override | |
public void onAudioFormatEnabled(Format format, int trigger, long mediaTimeMs) { | |
Log.d(TAG, "audioFormat [" + getSessionTimeString() + ", " + format.id + ", " | |
+ Integer.toString(trigger) + "]"); | |
} | |
// DemoPlayer.InternalErrorListener | |
@Override | |
public void onLoadError(int sourceId, IOException e) { | |
printInternalError("loadError", e); | |
} | |
@Override | |
public void onRendererInitializationError(Exception e) { | |
printInternalError("rendererInitError", e); | |
} | |
@Override | |
public void onDrmSessionManagerError(Exception e) { | |
printInternalError("drmSessionManagerError", e); | |
} | |
@Override | |
public void onDecoderInitializationError(DecoderInitializationException e) { | |
printInternalError("decoderInitializationError", e); | |
} | |
@Override | |
public void onAudioTrackInitializationError(AudioTrack.InitializationException e) { | |
printInternalError("audioTrackInitializationError", e); | |
} | |
@Override | |
public void onAudioTrackWriteError(AudioTrack.WriteException e) { | |
printInternalError("audioTrackWriteError", e); | |
} | |
@Override | |
public void onCryptoError(CryptoException e) { | |
printInternalError("cryptoError", e); | |
} | |
@Override | |
public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs, | |
long initializationDurationMs) { | |
Log.d(TAG, "decoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]"); | |
} | |
@Override | |
public void onAvailableRangeChanged(TimeRange availableRange) { | |
availableRangeValuesUs = availableRange.getCurrentBoundsUs(availableRangeValuesUs); | |
Log.d(TAG, "availableRange [" + availableRange.isStatic() + ", " + availableRangeValuesUs[0] | |
+ ", " + availableRangeValuesUs[1] + "]"); | |
} | |
private void printInternalError(String type, Exception e) { | |
Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e); | |
} | |
private String getStateString(int state) { | |
switch (state) { | |
case ExoPlayer.STATE_BUFFERING: | |
return "B"; | |
case ExoPlayer.STATE_ENDED: | |
return "E"; | |
case ExoPlayer.STATE_IDLE: | |
return "I"; | |
case ExoPlayer.STATE_PREPARING: | |
return "P"; | |
case ExoPlayer.STATE_READY: | |
return "R"; | |
default: | |
return "?"; | |
} | |
} | |
private String getSessionTimeString() { | |
return getTimeString(SystemClock.elapsedRealtime() - sessionStartTimeMs); | |
} | |
private String getTimeString(long timeMs) { | |
return TIME_FORMAT.format((timeMs) / 1000f); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2014 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.earthlinktele.faisaljamil.cinemana.player; | |
import android.content.Context; | |
import android.media.MediaCodec; | |
import android.net.Uri; | |
import com.earthlinktele.faisaljamil.cinemana.player.CinemanaPlayer.RendererBuilder; | |
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; | |
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; | |
import com.google.android.exoplayer.MediaFormat; | |
import com.google.android.exoplayer.SingleSampleSource; | |
import com.google.android.exoplayer.TrackRenderer; | |
import com.google.android.exoplayer.audio.AudioCapabilities; | |
import com.google.android.exoplayer.extractor.Extractor; | |
import com.google.android.exoplayer.extractor.ExtractorSampleSource; | |
import com.google.android.exoplayer.text.TextTrackRenderer; | |
import com.google.android.exoplayer.upstream.Allocator; | |
import com.google.android.exoplayer.upstream.DataSource; | |
import com.google.android.exoplayer.upstream.DefaultAllocator; | |
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; | |
import com.google.android.exoplayer.upstream.DefaultUriDataSource; | |
import com.google.android.exoplayer.util.MimeTypes; | |
/** | |
* A {@link RendererBuilder} for streams that can be read using an {@link Extractor}. | |
*/ | |
public class ExtractorRendererBuilder implements RendererBuilder { | |
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024; | |
private static final int BUFFER_SEGMENT_COUNT = 256; | |
private final Context context; | |
private final String userAgent; | |
private final Uri uri; | |
public final Uri subTitleUrl; | |
public ExtractorRendererBuilder(Context context, String userAgent, Uri uri, Uri subTitleUri) { | |
this.context = context; | |
this.userAgent = userAgent; | |
this.uri = uri; | |
this.subTitleUrl = subTitleUri; | |
} | |
@Override | |
public void buildRenderers(CinemanaPlayer player) { | |
Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE); | |
// Build the video and audio renderers. | |
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(player.getMainHandler(), | |
null); | |
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); | |
ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, allocator, | |
BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE); | |
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, | |
sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, player.getMainHandler(), | |
player, 50); | |
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource, | |
null, true, player.getMainHandler(), player, AudioCapabilities.getCapabilities(context)); | |
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); | |
SingleSampleSource textSampleSource = new SingleSampleSource(subTitleUrl, textDataSource, | |
MediaFormat.createTextFormat(MediaFormat.NO_VALUE,MimeTypes.APPLICATION_SUBRIP, MediaFormat.NO_VALUE, TrackRenderer.MATCH_LONGEST_US, null)); | |
TrackRenderer textRenderer = new TextTrackRenderer(textSampleSource, player, | |
player.getMainHandler().getLooper()); | |
// Invoke the callback. | |
TrackRenderer[] renderers = new TrackRenderer[CinemanaPlayer.RENDERER_COUNT]; | |
renderers[CinemanaPlayer.TYPE_VIDEO] = videoRenderer; | |
renderers[CinemanaPlayer.TYPE_AUDIO] = audioRenderer; | |
renderers[CinemanaPlayer.TYPE_TEXT] = textRenderer; | |
player.onRenderers(renderers, bandwidthMeter); | |
//text renderer is disabled by default initially. So, enabling it here. | |
player.setSelectedTrack(CinemanaPlayer.TYPE_TEXT, CinemanaPlayer.TRACK_DEFAULT); | |
//disable subtitlin | |
//player.selectTrack(CinemanaPlayer.TYPE_TEXT, CinemanaPlayer.DISABLED_TRACK); | |
} | |
@Override | |
public void cancel() { | |
// Do nothing. | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2014 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.earthlinktele.faisaljamil.cinemana.player; | |
import android.content.Context; | |
import android.media.MediaCodec; | |
import android.os.Handler; | |
import com.earthlinktele.faisaljamil.cinemana.player.CinemanaPlayer.RendererBuilder; | |
import com.google.android.exoplayer.DefaultLoadControl; | |
import com.google.android.exoplayer.LoadControl; | |
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; | |
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException; | |
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; | |
import com.google.android.exoplayer.TrackRenderer; | |
import com.google.android.exoplayer.audio.AudioCapabilities; | |
import com.google.android.exoplayer.chunk.VideoFormatSelectorUtil; | |
import com.google.android.exoplayer.hls.HlsChunkSource; | |
import com.google.android.exoplayer.hls.HlsMasterPlaylist; | |
import com.google.android.exoplayer.hls.HlsPlaylist; | |
import com.google.android.exoplayer.hls.HlsPlaylistParser; | |
import com.google.android.exoplayer.hls.HlsSampleSource; | |
import com.google.android.exoplayer.metadata.Id3Parser; | |
import com.google.android.exoplayer.metadata.MetadataTrackRenderer; | |
import com.google.android.exoplayer.text.eia608.Eia608TrackRenderer; | |
import com.google.android.exoplayer.upstream.DataSource; | |
import com.google.android.exoplayer.upstream.DefaultAllocator; | |
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; | |
import com.google.android.exoplayer.upstream.DefaultUriDataSource; | |
import com.google.android.exoplayer.util.ManifestFetcher; | |
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback; | |
import java.io.IOException; | |
import java.util.Map; | |
/** | |
* A {@link RendererBuilder} for HLS. | |
*/ | |
public class HlsRendererBuilder implements RendererBuilder { | |
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024; | |
private static final int BUFFER_SEGMENTS = 256; | |
private final Context context; | |
private final String userAgent; | |
private final String url; | |
private AsyncRendererBuilder currentAsyncBuilder; | |
public HlsRendererBuilder(Context context, String userAgent, String url) { | |
this.context = context; | |
this.userAgent = userAgent; | |
this.url = url; | |
} | |
@Override | |
public void buildRenderers(CinemanaPlayer player) { | |
currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, player); | |
currentAsyncBuilder.init(); | |
} | |
@Override | |
public void cancel() { | |
if (currentAsyncBuilder != null) { | |
currentAsyncBuilder.cancel(); | |
currentAsyncBuilder = null; | |
} | |
} | |
private static final class AsyncRendererBuilder implements ManifestCallback<HlsPlaylist> { | |
private final Context context; | |
private final String userAgent; | |
private final String url; | |
private final CinemanaPlayer player; | |
private final ManifestFetcher<HlsPlaylist> playlistFetcher; | |
private boolean canceled; | |
public AsyncRendererBuilder(Context context, String userAgent, String url, CinemanaPlayer player) { | |
this.context = context; | |
this.userAgent = userAgent; | |
this.url = url; | |
this.player = player; | |
HlsPlaylistParser parser = new HlsPlaylistParser(); | |
playlistFetcher = new ManifestFetcher<>(url, new DefaultUriDataSource(context, userAgent), | |
parser); | |
} | |
public void init() { | |
playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this); | |
} | |
public void cancel() { | |
canceled = true; | |
} | |
@Override | |
public void onSingleManifestError(IOException e) { | |
if (canceled) { | |
return; | |
} | |
player.onRenderersError(e); | |
} | |
@Override | |
public void onSingleManifest(HlsPlaylist manifest) { | |
if (canceled) { | |
return; | |
} | |
Handler mainHandler = player.getMainHandler(); | |
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); | |
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); | |
int[] variantIndices = null; | |
if (manifest instanceof HlsMasterPlaylist) { | |
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) manifest; | |
try { | |
variantIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay( | |
context, masterPlaylist.variants, null, false); | |
} catch (DecoderQueryException e) { | |
player.onRenderersError(e); | |
return; | |
} | |
if (variantIndices.length == 0) { | |
player.onRenderersError(new IllegalStateException("No variants selected.")); | |
return; | |
} | |
} | |
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); | |
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter, | |
variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE); | |
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl, | |
BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, CinemanaPlayer.TYPE_VIDEO); | |
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, | |
sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50); | |
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource, | |
null, true, player.getMainHandler(), player, AudioCapabilities.getCapabilities(context)); | |
MetadataTrackRenderer<Map<String, Object>> id3Renderer = new MetadataTrackRenderer<>( | |
sampleSource, new Id3Parser(), player, mainHandler.getLooper()); | |
Eia608TrackRenderer closedCaptionRenderer = new Eia608TrackRenderer(sampleSource, player, | |
mainHandler.getLooper()); | |
TrackRenderer[] renderers = new TrackRenderer[CinemanaPlayer.RENDERER_COUNT]; | |
renderers[CinemanaPlayer.TYPE_VIDEO] = videoRenderer; | |
renderers[CinemanaPlayer.TYPE_AUDIO] = audioRenderer; | |
renderers[CinemanaPlayer.TYPE_METADATA] = id3Renderer; | |
renderers[CinemanaPlayer.TYPE_TEXT] = closedCaptionRenderer; | |
player.onRenderers(renderers, bandwidthMeter); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2014 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.earthlinktele.faisaljamil.cinemana.player; | |
import android.content.Context; | |
import android.media.MediaCodec; | |
import android.os.Handler; | |
import com.google.android.exoplayer.DefaultLoadControl; | |
import com.google.android.exoplayer.LoadControl; | |
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; | |
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; | |
import com.google.android.exoplayer.TrackRenderer; | |
import com.google.android.exoplayer.audio.AudioCapabilities; | |
import com.google.android.exoplayer.chunk.ChunkSampleSource; | |
import com.google.android.exoplayer.chunk.ChunkSource; | |
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator; | |
import com.google.android.exoplayer.drm.DrmSessionManager; | |
import com.google.android.exoplayer.drm.MediaDrmCallback; | |
import com.google.android.exoplayer.drm.StreamingDrmSessionManager; | |
import com.google.android.exoplayer.drm.UnsupportedDrmException; | |
import com.google.android.exoplayer.smoothstreaming.DefaultSmoothStreamingTrackSelector; | |
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource; | |
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest; | |
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement; | |
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifestParser; | |
import com.google.android.exoplayer.text.TextTrackRenderer; | |
import com.google.android.exoplayer.upstream.DataSource; | |
import com.google.android.exoplayer.upstream.DefaultAllocator; | |
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; | |
import com.google.android.exoplayer.upstream.DefaultHttpDataSource; | |
import com.google.android.exoplayer.upstream.DefaultUriDataSource; | |
import com.google.android.exoplayer.util.ManifestFetcher; | |
import com.google.android.exoplayer.util.Util; | |
import com.earthlinktele.faisaljamil.cinemana.player.CinemanaPlayer.RendererBuilder; | |
import java.io.IOException; | |
/** | |
* A {@link RendererBuilder} for SmoothStreaming. | |
*/ | |
public class SmoothStreamingRendererBuilder implements RendererBuilder { | |
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024; | |
private static final int VIDEO_BUFFER_SEGMENTS = 200; | |
private static final int AUDIO_BUFFER_SEGMENTS = 54; | |
private static final int TEXT_BUFFER_SEGMENTS = 2; | |
private static final int LIVE_EDGE_LATENCY_MS = 30000; | |
private final Context context; | |
private final String userAgent; | |
private final String url; | |
private final MediaDrmCallback drmCallback; | |
private AsyncRendererBuilder currentAsyncBuilder; | |
public SmoothStreamingRendererBuilder(Context context, String userAgent, String url, | |
MediaDrmCallback drmCallback) { | |
this.context = context; | |
this.userAgent = userAgent; | |
this.url = Util.toLowerInvariant(url).endsWith("/manifest") ? url : url + "/Manifest"; | |
this.drmCallback = drmCallback; | |
} | |
@Override | |
public void buildRenderers(CinemanaPlayer player) { | |
currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, drmCallback, player); | |
currentAsyncBuilder.init(); | |
} | |
@Override | |
public void cancel() { | |
if (currentAsyncBuilder != null) { | |
currentAsyncBuilder.cancel(); | |
currentAsyncBuilder = null; | |
} | |
} | |
private static final class AsyncRendererBuilder | |
implements ManifestFetcher.ManifestCallback<SmoothStreamingManifest> { | |
private final Context context; | |
private final String userAgent; | |
private final MediaDrmCallback drmCallback; | |
private final CinemanaPlayer player; | |
private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher; | |
private boolean canceled; | |
public AsyncRendererBuilder(Context context, String userAgent, String url, | |
MediaDrmCallback drmCallback, CinemanaPlayer player) { | |
this.context = context; | |
this.userAgent = userAgent; | |
this.drmCallback = drmCallback; | |
this.player = player; | |
SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser(); | |
manifestFetcher = new ManifestFetcher<>(url, new DefaultHttpDataSource(userAgent, null), | |
parser); | |
} | |
public void init() { | |
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this); | |
} | |
public void cancel() { | |
canceled = true; | |
} | |
@Override | |
public void onSingleManifestError(IOException exception) { | |
if (canceled) { | |
return; | |
} | |
player.onRenderersError(exception); | |
} | |
@Override | |
public void onSingleManifest(SmoothStreamingManifest manifest) { | |
if (canceled) { | |
return; | |
} | |
Handler mainHandler = player.getMainHandler(); | |
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE)); | |
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player); | |
// Check drm support if necessary. | |
DrmSessionManager drmSessionManager = null; | |
if (manifest.protectionElement != null) { | |
if (Util.SDK_INT < 18) { | |
player.onRenderersError( | |
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME)); | |
return; | |
} | |
try { | |
drmSessionManager = new StreamingDrmSessionManager(manifest.protectionElement.uuid, | |
player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player); | |
} catch (UnsupportedDrmException e) { | |
player.onRenderersError(e); | |
return; | |
} | |
} | |
// Build the video renderer. | |
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); | |
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher, | |
new DefaultSmoothStreamingTrackSelector(context, StreamElement.TYPE_VIDEO), | |
videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS); | |
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, | |
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, | |
CinemanaPlayer.TYPE_VIDEO); | |
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, videoSampleSource, | |
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, drmSessionManager, true, mainHandler, | |
player, 50); | |
// Build the audio renderer. | |
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); | |
ChunkSource audioChunkSource = new SmoothStreamingChunkSource(manifestFetcher, | |
new DefaultSmoothStreamingTrackSelector(context, StreamElement.TYPE_AUDIO), | |
audioDataSource, null, LIVE_EDGE_LATENCY_MS); | |
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl, | |
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, | |
CinemanaPlayer.TYPE_AUDIO); | |
TrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource, | |
drmSessionManager, true, mainHandler, player, AudioCapabilities.getCapabilities(context)); | |
// Build the text renderer. | |
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); | |
ChunkSource textChunkSource = new SmoothStreamingChunkSource(manifestFetcher, | |
new DefaultSmoothStreamingTrackSelector(context, StreamElement.TYPE_TEXT), | |
textDataSource, null, LIVE_EDGE_LATENCY_MS); | |
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl, | |
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, | |
CinemanaPlayer.TYPE_TEXT); | |
TrackRenderer textRenderer = new TextTrackRenderer(textSampleSource, player, | |
mainHandler.getLooper()); | |
// Invoke the callback. | |
TrackRenderer[] renderers = new TrackRenderer[CinemanaPlayer.RENDERER_COUNT]; | |
renderers[CinemanaPlayer.TYPE_VIDEO] = videoRenderer; | |
renderers[CinemanaPlayer.TYPE_AUDIO] = audioRenderer; | |
renderers[CinemanaPlayer.TYPE_TEXT] = textRenderer; | |
player.onRenderers(renderers, bandwidthMeter); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2014 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.earthlinktele.faisaljamil.cinemana.player; | |
import android.annotation.TargetApi; | |
import android.media.MediaDrm.KeyRequest; | |
import android.media.MediaDrm.ProvisionRequest; | |
import android.text.TextUtils; | |
import com.google.android.exoplayer.drm.MediaDrmCallback; | |
import com.google.android.exoplayer.drm.StreamingDrmSessionManager; | |
import com.google.android.exoplayer.util.Util; | |
import java.io.IOException; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.UUID; | |
/** | |
* Demo {@link StreamingDrmSessionManager} for smooth streaming test content. | |
*/ | |
@TargetApi(18) | |
public class SmoothStreamingTestMediaDrmCallback implements MediaDrmCallback { | |
private static final String PLAYREADY_TEST_DEFAULT_URI = | |
"http://playready.directtaps.net/pr/svc/rightsmanager.asmx"; | |
private static final Map<String, String> KEY_REQUEST_PROPERTIES; | |
static { | |
HashMap<String, String> keyRequestProperties = new HashMap<>(); | |
keyRequestProperties.put("Content-Type", "text/xml"); | |
keyRequestProperties.put("SOAPAction", | |
"http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"); | |
KEY_REQUEST_PROPERTIES = keyRequestProperties; | |
} | |
@Override | |
public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException { | |
String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData()); | |
return Util.executePost(url, null, null); | |
} | |
@Override | |
public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception { | |
String url = request.getDefaultUrl(); | |
if (TextUtils.isEmpty(url)) { | |
url = PLAYREADY_TEST_DEFAULT_URI; | |
} | |
return Util.executePost(url, request.getData(), KEY_REQUEST_PROPERTIES); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2014 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.earthlinktele.faisaljamil.cinemana.player; | |
import android.annotation.TargetApi; | |
import android.media.MediaDrm.KeyRequest; | |
import android.media.MediaDrm.ProvisionRequest; | |
import android.text.TextUtils; | |
import com.google.android.exoplayer.drm.MediaDrmCallback; | |
import com.google.android.exoplayer.util.Util; | |
import java.io.IOException; | |
import java.util.UUID; | |
/** | |
* A {@link MediaDrmCallback} for Widevine test content. | |
*/ | |
@TargetApi(18) | |
public class WidevineTestMediaDrmCallback implements MediaDrmCallback { | |
private static final String WIDEVINE_GTS_DEFAULT_BASE_URI = | |
"http://wv-staging-proxy.appspot.com/proxy?provider=YouTube&video_id="; | |
private final String defaultUri; | |
public WidevineTestMediaDrmCallback(String videoId) { | |
defaultUri = WIDEVINE_GTS_DEFAULT_BASE_URI + videoId; | |
} | |
@Override | |
public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException { | |
String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData()); | |
return Util.executePost(url, null, null); | |
} | |
@Override | |
public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws IOException { | |
String url = request.getDefaultUrl(); | |
if (TextUtils.isEmpty(url)) { | |
url = defaultUri; | |
} | |
return Util.executePost(url, request.getData(), null); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment