Slide 1

Slide 1 text

Using Exoplayer Effie Barak (@CodingChick)

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

What is Exoplayer ...and why is everyone talking about it

Slide 4

Slide 4 text

Udemy's Story

Slide 5

Slide 5 text

Media Player Simple integration, no extensibility mediaPlayer.setDataSource(url); mediaPlayer.prepare(); mediaPlayer.start();

Slide 6

Slide 6 text

@Override public boolean onError(MediaPlayer mp, int what, int extra) { mediaPlayerError = true; if(what == -38) { //This exception just Happens for varios unexplainable reasons UdemyMediaPlayer.this.prepareAsync(); } return false; }

Slide 7

Slide 7 text

This beautiful state machine is a lie

Slide 8

Slide 8 text

Exoplayer Very extensible, but steeper implementation curve

Slide 9

Slide 9 text

Solves these problems • Open source, written in Java • Built on top of Media Codec • Handles HLS correctly

Slide 10

Slide 10 text

Extensible and/ or supports • Background playing • Subtitles • Variable playing speed • Variable resolutions

Slide 11

Slide 11 text

Exoplayer basics

Slide 12

Slide 12 text

Tips to get started: • ExoPlayer is written in Java. • The sample app is a good place to start. • The default implementaions are Good Implementaions.

Slide 13

Slide 13 text

1st part The player player = ExoPlayer.Factory.newInstance( PlayerConstants.RENDERER_COUNT, MIN_BUFFER_MS, MIN_REBUFFER_MS); playerControl = new PlayerControl(player);

Slide 14

Slide 14 text

Adding state listeners public abstract class UdemyBaseExoplayer implements ExoPlayer.Listener, ChunkSampleSource.EventListener, HlsSampleSource.EventListener, DefaultBandwidthMeter.EventListener, MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener player.addListener(this);

Slide 15

Slide 15 text

2nd part The Core

Slide 16

Slide 16 text

The journey of a Stream

Slide 17

Slide 17 text

Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE); Handler mainHandler = player.getMainHandler(); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, null); DataSource dataSource = new DefaultUriDataSource( context, bandwidthMeter, Util.getUserAgent(mContext, Constants.UDEMY_NAME)); ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, allocator, BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE, mainHandler, player, 0); MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, sampleSource, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50); MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource, MediaCodecSelector.DEFAULT, null, true, mainHandler, player, AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC); TrackRenderer[] renderers = new TrackRenderer[PlayerConstants.RENDERER_COUNT]; renderers[PlayerConstants.TYPE_VIDEO] = videoRenderer; renderers[PlayerConstants.TYPE_AUDIO] = audioRenderer; player.onRenderers(renderers, bandwidthMeter);

Slide 18

Slide 18 text

// Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE); // Handler mainHandler = player.getMainHandler(); // DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, null); // DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, // Util.getUserAgent(mContext, Constants.UDEMY_NAME)); ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, allocator, BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE, mainHandler, player, 0); MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, sampleSource, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50); MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource, MediaCodecSelector.DEFAULT, null, true, mainHandler, player, AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC); TrackRenderer[] renderers = new TrackRenderer[PlayerConstants.RENDERER_COUNT]; renderers[PlayerConstants.TYPE_VIDEO] = videoRenderer; renderers[PlayerConstants.TYPE_AUDIO] = audioRenderer; player.onRenderers(renderers, bandwidthMeter);

Slide 19

Slide 19 text

Build extractors DefaultUriDataSource uriDataSource = new DefaultUriDataSource (context, bandwidthMeter, userAgent); ExtractorSampleSource sampleSource = new ExtractorSampleSource (uri, uriDataSource, allocator, PlayerConstants.BUFFER_SEGMENT_COUNT * PlayerConstants.BUFFER_SEGMENT_SIZE);

Slide 20

Slide 20 text

Build renderers TrackRenderer[] renderers = new TrackRenderer[PlayerConstants.RENDERER_COUNT]; MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer( sampleSource, MediaCodecSelector.DEFAULT, null, true, player.getMainHandler(), player, AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC); renderers[PlayerConstants.TYPE_AUDIO] = audioRenderer;

Slide 21

Slide 21 text

Connect renderers to the player player.prepare(renderers);

Slide 22

Slide 22 text

Udemy customizations to basic structure • We decreased the buffer size after getting OOM exceptions on low end devices. public static final int BUFFER_SEGMENT_SIZE = 16 * 1024; // Original value was 64 * 1024 public static final int VIDEO_BUFFER_SEGMENTS = 50; // Original value was 200 public static final int AUDIO_BUFFER_SEGMENTS = 20; // Original value was 54 public static final int BUFFER_SEGMENT_COUNT = 64; // Original value was 256

Slide 23

Slide 23 text

Udemy customizations to basic structure • We decreased the buffer size after getting OOM exceptions on low end devices. • The ExtractorSampleSource gets a list of possible extractors to work with (mp3 and mp4 only) mp4Extractor = new Mp4Extractor(); mp3Extractor = new Mp3Extractor(); sampleSource = new ExtractorSampleSource(..., mp4Extractor, mp3Extractor);

Slide 24

Slide 24 text

!

Slide 25

Slide 25 text

HLS

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

HLS File

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider(); HlsChunkSource chunkSource = new HlsChunkSource(..., uriDataSource, url,..., bandwidthMeter, timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE);

Slide 30

Slide 30 text

sampleSource = new HlsSampleSource(chunkSource, ...);

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

The good news HlsChunkSource chunkSource = new HlsChunkSource(..., HlsChunkSource.ADAPTIVE_MODE_NONE);

Slide 33

Slide 33 text

The bad news // The index in variants of the currently selected variant. private int selectedVariantIndex; public void getChunkOperation(...) { int nextVariantIndex; ... if (adaptiveMode == ADAPTIVE_MODE_NONE) { nextVariantIndex = selectedVariantIndex; switchingVariantSpliced = false; } else { ... } ...

Slide 34

Slide 34 text

!

Slide 35

Slide 35 text

Background Media Playing

Slide 36

Slide 36 text

Clear the surface by sending a message to ExoPlayer that sets the surface to null player.blockingSendMessage( videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, null);

Slide 37

Slide 37 text

Create a service to keep the app alive. Both the view and the service control the same instance of the player, stored globally.

Slide 38

Slide 38 text

When the app resumes set the surface again setPlayerSurface(surfaceView.getHolder().getSurface()); And in the player public void setSurface(Surface surface) { this.surface = surface; pushSurface(false); }

Slide 39

Slide 39 text

Subtitles • Support .srt • Don't crash the video in case of error • Support multiple formats such as UTF-8

Slide 40

Slide 40 text

Feed subtitles manually :(

Slide 41

Slide 41 text

public void displayExoplayerSubtitles( File file, final MediaController.MediaPlayerControl playerControl, final ViewGroup subtitleLayout, final Context context) { convertFileCaptionList(file, context); runnableCode = new Runnable() { @Override public void run() { displayForPosition(playerControl.getCurrentPosition(), subtitleLayout, context); handler.postDelayed(runnableCode, 200); } }; handler.post(runnableCode); }

Slide 42

Slide 42 text

Subtitles conversion https://github.com/JDaren/subtitleConverter

Slide 43

Slide 43 text

Variable playback speeds

Slide 44

Slide 44 text

Video always follows Audio

Slide 45

Slide 45 text

Sonic library (The Java edition) https://github.com/waywardgeek/sonic/blob/master/ Sonic.java

Slide 46

Slide 46 text

Implementation

Slide 47

Slide 47 text

public class VariableSpeedAudioRenderer extends MediaCodecAudioTrackRenderer

Slide 48

Slide 48 text

Method to override private byte[] sonicInputBuffer; private byte[] sonicOutputBuffer; @Override protected void onOutputFormatChanged(final MediaFormat format) {

Slide 49

Slide 49 text

Method body // Two samples per frame * 2 to support audio speeds down to 0.5 final int bufferSizeBytes = SAMPLES_PER_CODEC_FRAME * 2 * 2 * channelCount; this.sonicInputBuffer = new byte[bufferSizeBytes]; this.sonicOutputBuffer = new byte[bufferSizeBytes]; this.sonic = new Sonic( format.getInteger(MediaFormat.KEY_SAMPLE_RATE), format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)); this.lastInternalBuffer = ByteBuffer.wrap(sonicOutputBuffer, 0, 0); sonic.flushStream(); sonic.setSpeed(audioSpeed);

Slide 50

Slide 50 text

@Override protected boolean processOutputBuffer(..., final ByteBuffer buffer,...)

Slide 51

Slide 51 text

private ByteBuffer lastInternalBuffer; buffer.get(sonicInputBuffer, 0, bytesToRead); sonic.writeBytesToStream(sonicInputBuffer, bytesToRead); sonic.readBytesFromStream(sonicOutputBuffer, sonicOutputBuffer.length); return super.processOutputBuffer(..., lastInternalBuffer, ...);

Slide 52

Slide 52 text

Some optimization if (bufferIndex == lastSeenBufferIndex) { return super.processOutputBuffer(..., lastInternalBuffer, ..., bufferIndex, ...); } else { lastSeenBufferIndex = bufferIndex; }

Slide 53

Slide 53 text

To change the speed in the middle of consuming a buffer if (wasSpeedChanged) { sonic.flushStream(); sonic.setSpeed(audioSpeed); }

Slide 54

Slide 54 text

Thank you!