From e3f7dc18f93d29bab52381e3fba978a79d3d07e3 Mon Sep 17 00:00:00 2001
From: sebastien <sebastien.chauvin@gmail.com>
Date: Tue, 26 Jun 2018 08:00:43 +0200
Subject: [PATCH 01/23] Remove useless code

---
 .../src/main/java/ch/srg/mediaplayer/SRGMediaPlayerView.java | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerView.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerView.java
index a3a5f66..eb17151 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerView.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerView.java
@@ -286,11 +286,6 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
         }
     }
 
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-    }
-
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final int specWidth = MeasureSpec.getSize(widthMeasureSpec);

From ba3740762d519c6c2cbb0bc72173251a4df4b0a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Mon, 16 Jul 2018 09:45:14 +0200
Subject: [PATCH 02/23] Add DRM, hardcoded licence for quick tests, WIP

---
 .../mediaplayer/SRGMediaPlayerController.java | 33 ++++++++++++++++---
 1 file changed, 28 insertions(+), 5 deletions(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index 4d01247..a410d33 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -34,6 +34,12 @@
 import com.google.android.exoplayer2.Timeline;
 import com.google.android.exoplayer2.audio.AudioCapabilities;
 import com.google.android.exoplayer2.audio.AudioCapabilitiesReceiver;
+import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
+import com.google.android.exoplayer2.drm.DrmSessionManager;
+import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
+import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
+import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
+import com.google.android.exoplayer2.drm.UnsupportedDrmException;
 import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
 import com.google.android.exoplayer2.source.ExtractorMediaSource;
 import com.google.android.exoplayer2.source.MediaSource;
@@ -55,6 +61,7 @@
 import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
 import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
 import com.google.android.exoplayer2.upstream.FileDataSourceFactory;
+import com.google.android.exoplayer2.util.Util;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -450,6 +457,9 @@ public interface Listener {
 
     @Nullable
     private AkamaiMediaAnalyticsConfiguration akamaiMediaAnalyticsConfiguration;
+    // FIXME : why userAgent letterbox is set here?
+    private static final String userAgent = "curl/Letterbox_2.0"; // temporarily using curl/ user agent to force subtitles with Akamai beta
+    private static final String LICENCE_URL = "https://rng.stage.ott.irdeto.com/licenseServer/widevine/v1/SRG/license?contentId=srg-content";
 
     /**
      * Create a new SRGMediaPlayerController with the current context, a mediaPlayerDataProvider, and a TAG
@@ -475,15 +485,29 @@ public SRGMediaPlayerController(Context context, String tag) {
 
         trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
         eventLogger = new EventLogger(trackSelector);
-        DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this.context, null, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER);
+        DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
+        //DRM is only supported at API level 18+
+        if (Util.SDK_INT >= 18) {
+            try {
+                HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(LICENCE_URL, new DefaultHttpDataSourceFactory(userAgent));
+                drmSessionManager = new DefaultDrmSessionManager<>(C.WIDEVINE_UUID,
+                        FrameworkMediaDrm.newInstance(C.WIDEVINE_UUID),
+                        drmCallback, null, mainHandler, eventLogger);
+            } catch (UnsupportedDrmException e) {
+                // TODO : Post DRMErrorEvent
+                // fatalError = e;
+                e.printStackTrace();
+            }
+        }
+        DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this.context, drmSessionManager, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER);
 
         exoPlayer = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector, new DefaultLoadControl());
         exoPlayer.addListener(this);
-        exoPlayer.setVideoListener(this);
-        exoPlayer.setTextOutput(this);
+        exoPlayer.addVideoListener(this);
+        exoPlayer.addTextOutput(this);
         exoPlayer.setAudioDebugListener(eventLogger);
         exoPlayer.setVideoDebugListener(eventLogger);
-        exoPlayer.setMetadataOutput(eventLogger);
+        exoPlayer.addMetadataOutput(eventLogger);
         exoPlayerCurrentPlayWhenReady = exoPlayer.getPlayWhenReady();
 
         audioFocusChangeListener = new OnAudioFocusChangeListener(new WeakReference<>(this));
@@ -868,7 +892,6 @@ private void prepareInternal(@NonNull Uri videoUri, int streamType) throws SRGMe
             sendMessage(MSG_PLAYER_PREPARING);
             this.currentMediaUri = videoUri;
 
-            String userAgent = "curl/Letterbox_2.0"; // temporarily using curl/ user agent to force subtitles with Akamai beta
 
             DefaultHttpDataSourceFactory httpDataSourceFactory = new DefaultHttpDataSourceFactory(
                     userAgent,

From ff8717b81b8c8e78e80df7570d84385638d101cc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Mon, 16 Jul 2018 13:33:09 +0200
Subject: [PATCH 03/23] Restore exoplayer 2.5.1 methods

---
 .../java/ch/srg/mediaplayer/SRGMediaPlayerController.java | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index a410d33..ae7426b 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -503,11 +503,11 @@ public SRGMediaPlayerController(Context context, String tag) {
 
         exoPlayer = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector, new DefaultLoadControl());
         exoPlayer.addListener(this);
-        exoPlayer.addVideoListener(this);
-        exoPlayer.addTextOutput(this);
+        exoPlayer.setVideoListener(this);
+        exoPlayer.setTextOutput(this);
         exoPlayer.setAudioDebugListener(eventLogger);
         exoPlayer.setVideoDebugListener(eventLogger);
-        exoPlayer.addMetadataOutput(eventLogger);
+        exoPlayer.setMetadataOutput(eventLogger);
         exoPlayerCurrentPlayWhenReady = exoPlayer.getPlayWhenReady();
 
         audioFocusChangeListener = new OnAudioFocusChangeListener(new WeakReference<>(this));
@@ -908,7 +908,7 @@ private void prepareInternal(@NonNull Uri videoUri, int streamType) throws SRGMe
 
             switch (streamType) {
                 case STREAM_DASH:
-                    mediaSource = new DashMediaSource(videoUri, dataSourceFactory,
+                    mediaSource = new DashMediaSource(videoUri, new DefaultHttpDataSourceFactory(userAgent),
                             new DefaultDashChunkSource.Factory(dataSourceFactory), mainHandler, eventLogger);
                     break;
                 case STREAM_HLS:

From 96b622c510f3e16b25eb110cf5595e63296b2969 Mon Sep 17 00:00:00 2001
From: sebastien <sebastien.chauvin@gmail.com>
Date: Tue, 17 Jul 2018 10:19:53 +0200
Subject: [PATCH 04/23] WIP: try custom DashChunkSource to circumvent track
 selection issue

---
 .../mediaplayer/DefaultDashChunkSource.java   | 510 ++++++++++++++++++
 .../mediaplayer/SRGMediaPlayerController.java |   3 +-
 2 files changed, 511 insertions(+), 2 deletions(-)
 create mode 100644 srgmediaplayer/src/main/java/ch/srg/mediaplayer/DefaultDashChunkSource.java

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/DefaultDashChunkSource.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/DefaultDashChunkSource.java
new file mode 100644
index 0000000..c7cd217
--- /dev/null
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/DefaultDashChunkSource.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2016 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 ch.srg.mediaplayer;
+
+import android.net.Uri;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.extractor.ChunkIndex;
+import com.google.android.exoplayer2.extractor.Extractor;
+import com.google.android.exoplayer2.extractor.SeekMap;
+import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
+import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
+import com.google.android.exoplayer2.extractor.rawcc.RawCcExtractor;
+import com.google.android.exoplayer2.source.BehindLiveWindowException;
+import com.google.android.exoplayer2.source.chunk.Chunk;
+import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper;
+import com.google.android.exoplayer2.source.chunk.ChunkHolder;
+import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil;
+import com.google.android.exoplayer2.source.chunk.ContainerMediaChunk;
+import com.google.android.exoplayer2.source.chunk.InitializationChunk;
+import com.google.android.exoplayer2.source.chunk.MediaChunk;
+import com.google.android.exoplayer2.source.chunk.SingleSampleMediaChunk;
+import com.google.android.exoplayer2.source.dash.DashChunkSource;
+import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
+import com.google.android.exoplayer2.source.dash.DashWrappingSegmentIndex;
+import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
+import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
+import com.google.android.exoplayer2.source.dash.manifest.RangedUri;
+import com.google.android.exoplayer2.source.dash.manifest.Representation;
+import com.google.android.exoplayer2.trackselection.TrackSelection;
+import com.google.android.exoplayer2.upstream.DataSource;
+import com.google.android.exoplayer2.upstream.DataSpec;
+import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException;
+import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
+import com.google.android.exoplayer2.util.MimeTypes;
+import com.google.android.exoplayer2.util.Util;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A default {@link DashChunkSource} implementation.
+ */
+public class DefaultDashChunkSource implements DashChunkSource {
+
+    public static final class Factory implements DashChunkSource.Factory {
+
+        private final DataSource.Factory dataSourceFactory;
+        private final int maxSegmentsPerLoad;
+
+        public Factory(DataSource.Factory dataSourceFactory) {
+            this(dataSourceFactory, 1);
+        }
+
+        public Factory(DataSource.Factory dataSourceFactory, int maxSegmentsPerLoad) {
+            this.dataSourceFactory = dataSourceFactory;
+            this.maxSegmentsPerLoad = maxSegmentsPerLoad;
+        }
+
+        @Override
+        public DashChunkSource createDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower,
+                                                     DashManifest manifest, int periodIndex, int[] adaptationSetIndices,
+                                                     TrackSelection trackSelection, int trackType, long elapsedRealtimeOffsetMs,
+                                                     boolean enableEventMessageTrack, boolean enableCea608Track) {
+            DataSource dataSource = dataSourceFactory.createDataSource();
+            return new DefaultDashChunkSource(manifestLoaderErrorThrower, manifest, periodIndex,
+                    adaptationSetIndices, trackSelection, trackType, dataSource, elapsedRealtimeOffsetMs,
+                    maxSegmentsPerLoad, enableEventMessageTrack, enableCea608Track);
+        }
+
+    }
+
+    private final LoaderErrorThrower manifestLoaderErrorThrower;
+    private final int[] adaptationSetIndices;
+    private final TrackSelection trackSelection;
+    private final int trackType;
+    private final RepresentationHolder[] representationHolders;
+    private final DataSource dataSource;
+    private final long elapsedRealtimeOffsetMs;
+    private final int maxSegmentsPerLoad;
+
+    private DashManifest manifest;
+    private int periodIndex;
+
+    private IOException fatalError;
+    private boolean missingLastSegment;
+
+    /**
+     * @param manifestLoaderErrorThrower Throws errors affecting loading of manifests.
+     * @param manifest                   The initial manifest.
+     * @param periodIndex                The index of the period in the manifest.
+     * @param adaptationSetIndices       The indices of the adaptation sets in the period.
+     * @param trackSelection             The track selection.
+     * @param trackType                  The type of the tracks in the selection.
+     * @param dataSource                 A {@link DataSource} suitable for loading the media data.
+     * @param elapsedRealtimeOffsetMs    If known, an estimate of the instantaneous difference between
+     *                                   server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified
+     *                                   as the server's unix time minus the local elapsed time. If unknown, set to 0.
+     * @param maxSegmentsPerLoad         The maximum number of segments to combine into a single request.
+     *                                   Note that segments will only be combined if their {@link Uri}s are the same and if their
+     *                                   data ranges are adjacent.
+     * @param enableEventMessageTrack    Whether the chunks generated by the source may output an event
+     *                                   message track.
+     * @param enableCea608Track          Whether the chunks generated by the source may output a CEA-608 track.
+     */
+    public DefaultDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower,
+                                  DashManifest manifest, int periodIndex, int[] adaptationSetIndices,
+                                  TrackSelection trackSelection, int trackType, DataSource dataSource,
+                                  long elapsedRealtimeOffsetMs, int maxSegmentsPerLoad, boolean enableEventMessageTrack,
+                                  boolean enableCea608Track) {
+        this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
+        this.manifest = manifest;
+        this.adaptationSetIndices = adaptationSetIndices;
+        this.trackSelection = trackSelection;
+        this.trackType = trackType;
+        this.dataSource = dataSource;
+        this.periodIndex = periodIndex;
+        this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs;
+        this.maxSegmentsPerLoad = maxSegmentsPerLoad;
+
+        long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
+        List<Representation> representations = getRepresentations();
+        representationHolders = new RepresentationHolder[trackSelection.length()];
+        for (int i = 0; i < representationHolders.length; i++) {
+            Representation representation = representations.get(trackSelection.getIndexInTrackGroup(i));
+            representationHolders[i] = new RepresentationHolder(periodDurationUs, representation,
+                    enableEventMessageTrack, enableCea608Track);
+        }
+    }
+
+    @Override
+    public void updateManifest(DashManifest newManifest, int newPeriodIndex) {
+        try {
+            manifest = newManifest;
+            periodIndex = newPeriodIndex;
+            long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
+            List<Representation> representations = getRepresentations();
+            for (int i = 0; i < representationHolders.length; i++) {
+                int index = trackSelection.getIndexInTrackGroup(i);
+                if (index >= representations.size()) {
+                    index = 0;
+                    Log.e("DefaultDashChunkSource", "invalid track index " + index + " (" + representations.size() + ") ");
+                }
+                Representation representation = representations.get(index);
+                representationHolders[i].updateRepresentation(periodDurationUs, representation);
+            }
+        } catch (BehindLiveWindowException e) {
+            fatalError = e;
+        }
+    }
+
+    @Override
+    public void maybeThrowError() throws IOException {
+        if (fatalError != null) {
+            throw fatalError;
+        } else {
+            manifestLoaderErrorThrower.maybeThrowError();
+        }
+    }
+
+    @Override
+    public int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) {
+        if (fatalError != null || trackSelection.length() < 2) {
+            return queue.size();
+        }
+        return trackSelection.evaluateQueueSize(playbackPositionUs, queue);
+    }
+
+    @Override
+    public final void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out) {
+        if (fatalError != null) {
+            return;
+        }
+
+        long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
+        trackSelection.updateSelectedTrack(bufferedDurationUs);
+
+        RepresentationHolder representationHolder =
+                representationHolders[trackSelection.getSelectedIndex()];
+
+        if (representationHolder.extractorWrapper != null) {
+            Representation selectedRepresentation = representationHolder.representation;
+            RangedUri pendingInitializationUri = null;
+            RangedUri pendingIndexUri = null;
+            if (representationHolder.extractorWrapper.getSampleFormats() == null) {
+                pendingInitializationUri = selectedRepresentation.getInitializationUri();
+            }
+            if (representationHolder.segmentIndex == null) {
+                pendingIndexUri = selectedRepresentation.getIndexUri();
+            }
+            if (pendingInitializationUri != null || pendingIndexUri != null) {
+                // We have initialization and/or index requests to make.
+                out.chunk = newInitializationChunk(representationHolder, dataSource,
+                        trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(),
+                        trackSelection.getSelectionData(), pendingInitializationUri, pendingIndexUri);
+                return;
+            }
+        }
+
+        long nowUs = getNowUnixTimeUs();
+        int availableSegmentCount = representationHolder.getSegmentCount();
+        if (availableSegmentCount == 0) {
+            // The index doesn't define any segments.
+            out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1);
+            return;
+        }
+
+        int firstAvailableSegmentNum = representationHolder.getFirstSegmentNum();
+        int lastAvailableSegmentNum;
+        if (availableSegmentCount == DashSegmentIndex.INDEX_UNBOUNDED) {
+            // The index is itself unbounded. We need to use the current time to calculate the range of
+            // available segments.
+            long liveEdgeTimeUs = nowUs - manifest.availabilityStartTime * 1000;
+            long periodStartUs = manifest.getPeriod(periodIndex).startMs * 1000;
+            long liveEdgeTimeInPeriodUs = liveEdgeTimeUs - periodStartUs;
+            if (manifest.timeShiftBufferDepth != C.TIME_UNSET) {
+                long bufferDepthUs = manifest.timeShiftBufferDepth * 1000;
+                firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum,
+                        representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs - bufferDepthUs));
+            }
+            // getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the
+            // index of the last completed segment.
+            lastAvailableSegmentNum = representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs) - 1;
+        } else {
+            lastAvailableSegmentNum = firstAvailableSegmentNum + availableSegmentCount - 1;
+        }
+
+        int segmentNum;
+        if (previous == null) {
+            segmentNum = Util.constrainValue(representationHolder.getSegmentNum(playbackPositionUs),
+                    firstAvailableSegmentNum, lastAvailableSegmentNum);
+        } else {
+            segmentNum = previous.getNextChunkIndex();
+            if (segmentNum < firstAvailableSegmentNum) {
+                // This is before the first chunk in the current manifest.
+                fatalError = new BehindLiveWindowException();
+                return;
+            }
+        }
+
+        if (segmentNum > lastAvailableSegmentNum
+                || (missingLastSegment && segmentNum >= lastAvailableSegmentNum)) {
+            // This is beyond the last chunk in the current manifest.
+            out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1);
+            return;
+        }
+
+        int maxSegmentCount = Math.min(maxSegmentsPerLoad, lastAvailableSegmentNum - segmentNum + 1);
+        out.chunk = newMediaChunk(representationHolder, dataSource, trackType,
+                trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(),
+                trackSelection.getSelectionData(), segmentNum, maxSegmentCount);
+    }
+
+    @Override
+    public void onChunkLoadCompleted(Chunk chunk) {
+        if (chunk instanceof InitializationChunk) {
+            InitializationChunk initializationChunk = (InitializationChunk) chunk;
+            RepresentationHolder representationHolder =
+                    representationHolders[trackSelection.indexOf(initializationChunk.trackFormat)];
+            // The null check avoids overwriting an index obtained from the manifest with one obtained
+            // from the stream. If the manifest defines an index then the stream shouldn't, but in cases
+            // where it does we should ignore it.
+            if (representationHolder.segmentIndex == null) {
+                SeekMap seekMap = representationHolder.extractorWrapper.getSeekMap();
+                if (seekMap != null) {
+                    representationHolder.segmentIndex = new DashWrappingSegmentIndex((ChunkIndex) seekMap);
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e) {
+        if (!cancelable) {
+            return false;
+        }
+        // Workaround for missing segment at the end of the period
+        if (!manifest.dynamic && chunk instanceof MediaChunk
+                && e instanceof InvalidResponseCodeException
+                && ((InvalidResponseCodeException) e).responseCode == 404) {
+            RepresentationHolder representationHolder =
+                    representationHolders[trackSelection.indexOf(chunk.trackFormat)];
+            int segmentCount = representationHolder.getSegmentCount();
+            if (segmentCount != DashSegmentIndex.INDEX_UNBOUNDED && segmentCount != 0) {
+                int lastAvailableSegmentNum = representationHolder.getFirstSegmentNum() + segmentCount - 1;
+                if (((MediaChunk) chunk).getNextChunkIndex() > lastAvailableSegmentNum) {
+                    missingLastSegment = true;
+                    return true;
+                }
+            }
+        }
+        // Blacklist if appropriate.
+        return ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection,
+                trackSelection.indexOf(chunk.trackFormat), e);
+    }
+
+    // Private methods.
+
+    private ArrayList<Representation> getRepresentations() {
+        List<AdaptationSet> manifestAdapationSets = manifest.getPeriod(periodIndex).adaptationSets;
+        ArrayList<Representation> representations = new ArrayList<>();
+        for (int adaptationSetIndex : adaptationSetIndices) {
+            representations.addAll(manifestAdapationSets.get(adaptationSetIndex).representations);
+        }
+        return representations;
+    }
+
+    private long getNowUnixTimeUs() {
+        if (elapsedRealtimeOffsetMs != 0) {
+            return (SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs) * 1000;
+        } else {
+            return System.currentTimeMillis() * 1000;
+        }
+    }
+
+    private static Chunk newInitializationChunk(RepresentationHolder representationHolder,
+                                                DataSource dataSource, Format trackFormat, int trackSelectionReason,
+                                                Object trackSelectionData, RangedUri initializationUri, RangedUri indexUri) {
+        RangedUri requestUri;
+        String baseUrl = representationHolder.representation.baseUrl;
+        if (initializationUri != null) {
+            // It's common for initialization and index data to be stored adjacently. Attempt to merge
+            // the two requests together to request both at once.
+            requestUri = initializationUri.attemptMerge(indexUri, baseUrl);
+            if (requestUri == null) {
+                requestUri = initializationUri;
+            }
+        } else {
+            requestUri = indexUri;
+        }
+        DataSpec dataSpec = new DataSpec(requestUri.resolveUri(baseUrl), requestUri.start,
+                requestUri.length, representationHolder.representation.getCacheKey());
+        return new InitializationChunk(dataSource, dataSpec, trackFormat,
+                trackSelectionReason, trackSelectionData, representationHolder.extractorWrapper);
+    }
+
+    private static Chunk newMediaChunk(RepresentationHolder representationHolder,
+                                       DataSource dataSource, int trackType, Format trackFormat, int trackSelectionReason,
+                                       Object trackSelectionData, int firstSegmentNum, int maxSegmentCount) {
+        Representation representation = representationHolder.representation;
+        long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum);
+        RangedUri segmentUri = representationHolder.getSegmentUrl(firstSegmentNum);
+        String baseUrl = representation.baseUrl;
+        if (representationHolder.extractorWrapper == null) {
+            long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum);
+            DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl),
+                    segmentUri.start, segmentUri.length, representation.getCacheKey());
+            return new SingleSampleMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason,
+                    trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, trackType, trackFormat);
+        } else {
+            int segmentCount = 1;
+            for (int i = 1; i < maxSegmentCount; i++) {
+                RangedUri nextSegmentUri = representationHolder.getSegmentUrl(firstSegmentNum + i);
+                RangedUri mergedSegmentUri = segmentUri.attemptMerge(nextSegmentUri, baseUrl);
+                if (mergedSegmentUri == null) {
+                    // Unable to merge segment fetches because the URIs do not merge.
+                    break;
+                }
+                segmentUri = mergedSegmentUri;
+                segmentCount++;
+            }
+            long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum + segmentCount - 1);
+            DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl),
+                    segmentUri.start, segmentUri.length, representation.getCacheKey());
+            long sampleOffsetUs = -representation.presentationTimeOffsetUs;
+            return new ContainerMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason,
+                    trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, segmentCount,
+                    sampleOffsetUs, representationHolder.extractorWrapper);
+        }
+    }
+
+    // Protected classes.
+
+    protected static final class RepresentationHolder {
+
+        public final ChunkExtractorWrapper extractorWrapper;
+
+        public Representation representation;
+        public DashSegmentIndex segmentIndex;
+
+        private long periodDurationUs;
+        private int segmentNumShift;
+
+        public RepresentationHolder(long periodDurationUs, Representation representation,
+                                    boolean enableEventMessageTrack, boolean enableCea608Track) {
+            this.periodDurationUs = periodDurationUs;
+            this.representation = representation;
+            String containerMimeType = representation.format.containerMimeType;
+            if (mimeTypeIsRawText(containerMimeType)) {
+                extractorWrapper = null;
+            } else {
+                Extractor extractor;
+                if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
+                    extractor = new RawCcExtractor(representation.format);
+                } else if (mimeTypeIsWebm(containerMimeType)) {
+                    extractor = new MatroskaExtractor(MatroskaExtractor.FLAG_DISABLE_SEEK_FOR_CUES);
+                } else {
+                    int flags = 0;
+                    if (enableEventMessageTrack) {
+                        flags |= FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK;
+                    }
+                    if (enableCea608Track) {
+                        flags |= FragmentedMp4Extractor.FLAG_ENABLE_CEA608_TRACK;
+                    }
+                    extractor = new FragmentedMp4Extractor(flags);
+                }
+                // Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream,
+                // as per DASH IF Interoperability Recommendations V3.0, 7.5.3.
+                extractorWrapper = new ChunkExtractorWrapper(extractor, representation.format);
+            }
+            segmentIndex = representation.getIndex();
+        }
+
+        public void updateRepresentation(long newPeriodDurationUs, Representation newRepresentation)
+                throws BehindLiveWindowException {
+            DashSegmentIndex oldIndex = representation.getIndex();
+            DashSegmentIndex newIndex = newRepresentation.getIndex();
+
+            periodDurationUs = newPeriodDurationUs;
+            representation = newRepresentation;
+            if (oldIndex == null) {
+                // Segment numbers cannot shift if the index isn't defined by the manifest.
+                return;
+            }
+
+            segmentIndex = newIndex;
+            if (!oldIndex.isExplicit()) {
+                // Segment numbers cannot shift if the index isn't explicit.
+                return;
+            }
+
+            int oldIndexSegmentCount = oldIndex.getSegmentCount(periodDurationUs);
+            if (oldIndexSegmentCount == 0) {
+                // Segment numbers cannot shift if the old index was empty.
+                return;
+            }
+
+            int oldIndexLastSegmentNum = oldIndex.getFirstSegmentNum() + oldIndexSegmentCount - 1;
+            long oldIndexEndTimeUs = oldIndex.getTimeUs(oldIndexLastSegmentNum)
+                    + oldIndex.getDurationUs(oldIndexLastSegmentNum, periodDurationUs);
+            int newIndexFirstSegmentNum = newIndex.getFirstSegmentNum();
+            long newIndexStartTimeUs = newIndex.getTimeUs(newIndexFirstSegmentNum);
+            if (oldIndexEndTimeUs == newIndexStartTimeUs) {
+                // The new index continues where the old one ended, with no overlap.
+                segmentNumShift += oldIndexLastSegmentNum + 1 - newIndexFirstSegmentNum;
+            } else if (oldIndexEndTimeUs < newIndexStartTimeUs) {
+                // There's a gap between the old index and the new one which means we've slipped behind the
+                // live window and can't proceed.
+                throw new BehindLiveWindowException();
+            } else {
+                // The new index overlaps with the old one.
+                segmentNumShift += oldIndex.getSegmentNum(newIndexStartTimeUs, periodDurationUs)
+                        - newIndexFirstSegmentNum;
+            }
+        }
+
+        public int getFirstSegmentNum() {
+            return segmentIndex.getFirstSegmentNum() + segmentNumShift;
+        }
+
+        public int getSegmentCount() {
+            return segmentIndex.getSegmentCount(periodDurationUs);
+        }
+
+        public long getSegmentStartTimeUs(int segmentNum) {
+            return segmentIndex.getTimeUs(segmentNum - segmentNumShift);
+        }
+
+        public long getSegmentEndTimeUs(int segmentNum) {
+            return getSegmentStartTimeUs(segmentNum)
+                    + segmentIndex.getDurationUs(segmentNum - segmentNumShift, periodDurationUs);
+        }
+
+        public int getSegmentNum(long positionUs) {
+            return segmentIndex.getSegmentNum(positionUs, periodDurationUs) + segmentNumShift;
+        }
+
+        public RangedUri getSegmentUrl(int segmentNum) {
+            return segmentIndex.getSegmentUrl(segmentNum - segmentNumShift);
+        }
+
+        private static boolean mimeTypeIsWebm(String mimeType) {
+            return mimeType.startsWith(MimeTypes.VIDEO_WEBM) || mimeType.startsWith(MimeTypes.AUDIO_WEBM)
+                    || mimeType.startsWith(MimeTypes.APPLICATION_WEBM);
+        }
+
+        private static boolean mimeTypeIsRawText(String mimeType) {
+            return MimeTypes.isText(mimeType) || MimeTypes.APPLICATION_TTML.equals(mimeType);
+        }
+
+    }
+
+}
diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index ae7426b..1edc922 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -46,7 +46,6 @@
 import com.google.android.exoplayer2.source.TrackGroup;
 import com.google.android.exoplayer2.source.TrackGroupArray;
 import com.google.android.exoplayer2.source.dash.DashMediaSource;
-import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
 import com.google.android.exoplayer2.source.hls.HlsMediaSource;
 import com.google.android.exoplayer2.text.Cue;
 import com.google.android.exoplayer2.text.TextRenderer;
@@ -489,7 +488,7 @@ public SRGMediaPlayerController(Context context, String tag) {
         //DRM is only supported at API level 18+
         if (Util.SDK_INT >= 18) {
             try {
-                HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(LICENCE_URL, new DefaultHttpDataSourceFactory(userAgent));
+                HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(LICENCE_URL, new DefaultHttpDataSourceFactory("Mozilla/5.0 (Linux; Android 7.1.1; F5321 Build/34.3.A.0.238) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36"));
                 drmSessionManager = new DefaultDrmSessionManager<>(C.WIDEVINE_UUID,
                         FrameworkMediaDrm.newInstance(C.WIDEVINE_UUID),
                         drmCallback, null, mainHandler, eventLogger);

From 2fa4454703b9d8da164f073260c1f223e6f0c927 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Mon, 23 Jul 2018 17:08:15 +0200
Subject: [PATCH 05/23] Manage playback drm error

---
 .../SRGDrmMediaPlayerException.java           | 20 ++++++++++++++++
 .../mediaplayer/SRGMediaPlayerController.java | 23 +++++++++++++++++++
 2 files changed, 43 insertions(+)
 create mode 100644 srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGDrmMediaPlayerException.java

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGDrmMediaPlayerException.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGDrmMediaPlayerException.java
new file mode 100644
index 0000000..26c52f1
--- /dev/null
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGDrmMediaPlayerException.java
@@ -0,0 +1,20 @@
+package ch.srg.mediaplayer;
+
+/**
+ * Copyright (c) SRG SSR. All rights reserved.
+ * <p>
+ * License information is available from the LICENSE file.
+ */
+public class SRGDrmMediaPlayerException extends SRGMediaPlayerException {
+    public SRGDrmMediaPlayerException(String detailMessage) {
+        super(detailMessage);
+    }
+
+    public SRGDrmMediaPlayerException(String detailMessage, Throwable throwable) {
+        super(detailMessage, throwable);
+    }
+
+    public SRGDrmMediaPlayerException(Throwable throwable) {
+        super(throwable);
+    }
+}
diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index 1edc922..26c808f 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -83,6 +83,7 @@
 @SuppressWarnings({"unused", "unchecked", "UnusedReturnValue", "WeakerAccess", "PointlessBitwiseExpression"})
 public class SRGMediaPlayerController implements Handler.Callback,
         Player.EventListener,
+        DefaultDrmSessionManager.EventListener,
         SimpleExoPlayer.VideoListener,
         AudioCapabilitiesReceiver.Listener,
         TextRenderer.Output {
@@ -2044,6 +2045,28 @@ public void onCues(List<Cue> cues) {
         sendMessage(MSG_PLAYER_SUBTITLE_CUES, cues);
     }
 
+    @Override
+    public void onDrmKeysLoaded() {
+        eventLogger.onDrmKeysLoaded();
+    }
+
+    @Override
+    public void onDrmSessionManagerError(Exception e) {
+        eventLogger.onDrmSessionManagerError(e);
+        postFatalErrorInternal(new SRGDrmMediaPlayerException(e));
+    }
+
+    @Override
+    public void onDrmKeysRestored() {
+        eventLogger.onDrmKeysRestored();
+    }
+
+    @Override
+    public void onDrmKeysRemoved() {
+        eventLogger.onDrmKeysRemoved();
+    }
+
+
     /**
      * Provide Akamai QOS Configuration.
      *

From 9c0e2304e36aefecd64ff027e6680246c6c7a50f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Mon, 23 Jul 2018 17:08:43 +0200
Subject: [PATCH 06/23] Add DrmConfig

---
 .../java/ch/srg/mediaplayer/DrmConfig.java    | 26 +++++++++++++++++++
 1 file changed, 26 insertions(+)
 create mode 100644 srgmediaplayer/src/main/java/ch/srg/mediaplayer/DrmConfig.java

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/DrmConfig.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/DrmConfig.java
new file mode 100644
index 0000000..b03107f
--- /dev/null
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/DrmConfig.java
@@ -0,0 +1,26 @@
+package ch.srg.mediaplayer;
+
+import java.util.UUID;
+
+/**
+ * Copyright (c) SRG SSR. All rights reserved.
+ * <p>
+ * License information is available from the LICENSE file.
+ */
+public class DrmConfig {
+    private String licenceUrl;
+    private UUID drmType;
+
+    public DrmConfig(String licenceUrl, UUID drmType) {
+        this.licenceUrl = licenceUrl;
+        this.drmType = drmType;
+    }
+
+    public String getLicenceUrl() {
+        return licenceUrl;
+    }
+
+    public UUID getDrmType() {
+        return drmType;
+    }
+}

From b5b881d71dd88dadf39484bbe733102faf629f4a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Mon, 23 Jul 2018 17:10:49 +0200
Subject: [PATCH 07/23] Configure DRM Session manager from DrmConfig

---
 .../mediaplayer/SRGMediaPlayerController.java | 29 +++++++++++--------
 1 file changed, 17 insertions(+), 12 deletions(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index 26c808f..204a432 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -275,7 +275,9 @@ public enum Type {
             /**
              * The Segment list has changed.
              */
-            SEGMENT_LIST_CHANGE
+            SEGMENT_LIST_CHANGE,
+
+            UNSUPPORTED_DRM
         }
 
         public final Type type;
@@ -459,7 +461,8 @@ public interface Listener {
     private AkamaiMediaAnalyticsConfiguration akamaiMediaAnalyticsConfiguration;
     // FIXME : why userAgent letterbox is set here?
     private static final String userAgent = "curl/Letterbox_2.0"; // temporarily using curl/ user agent to force subtitles with Akamai beta
-    private static final String LICENCE_URL = "https://rng.stage.ott.irdeto.com/licenseServer/widevine/v1/SRG/license?contentId=srg-content";
+    @Nullable
+    private DrmConfig drmConfig;
 
     /**
      * Create a new SRGMediaPlayerController with the current context, a mediaPlayerDataProvider, and a TAG
@@ -468,7 +471,7 @@ public interface Listener {
      * @param context context
      * @param tag     tag to identify this controller
      */
-    public SRGMediaPlayerController(Context context, String tag) {
+    public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig drmConfig) {
         this.context = context;
         this.mainHandler = new Handler(Looper.getMainLooper(), this);
         this.tag = tag;
@@ -486,17 +489,19 @@ public SRGMediaPlayerController(Context context, String tag) {
         trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
         eventLogger = new EventLogger(trackSelector);
         DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
-        //DRM is only supported at API level 18+
-        if (Util.SDK_INT >= 18) {
+        //&& Util.SDK_INT >= 18
+        if (drmConfig != null) {
             try {
-                HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(LICENCE_URL, new DefaultHttpDataSourceFactory("Mozilla/5.0 (Linux; Android 7.1.1; F5321 Build/34.3.A.0.238) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36"));
-                drmSessionManager = new DefaultDrmSessionManager<>(C.WIDEVINE_UUID,
-                        FrameworkMediaDrm.newInstance(C.WIDEVINE_UUID),
-                        drmCallback, null, mainHandler, eventLogger);
+                UUID drmType = drmConfig.getDrmType();
+                HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(drmConfig.getLicenceUrl(), new DefaultHttpDataSourceFactory(userAgent));
+                drmSessionManager = new DefaultDrmSessionManager<>(drmType,
+                        FrameworkMediaDrm.newInstance(drmType),
+                        drmCallback, null, mainHandler, this);
+                setViewType(ViewType.TYPE_SURFACEVIEW);
             } catch (UnsupportedDrmException e) {
-                // TODO : Post DRMErrorEvent
-                // fatalError = e;
-                e.printStackTrace();
+                Event event = Event.buildErrorEvent(this, e);
+                fatalError = event.exception;
+                postEventInternal(event);
             }
         }
         DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this.context, drmSessionManager, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER);

From 2bbd245451e6d814aea9e62e3a2b45c07f32198b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Mon, 23 Jul 2018 17:22:37 +0200
Subject: [PATCH 08/23] Error handling when drm not supported at MediaPlayer
 creation

---
 .../srg/mediaplayer/SRGMediaPlayerController.java   | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index 204a432..430f5dd 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -60,7 +60,6 @@
 import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
 import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
 import com.google.android.exoplayer2.upstream.FileDataSourceFactory;
-import com.google.android.exoplayer2.util.Util;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -71,6 +70,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.UUID;
 import java.util.WeakHashMap;
 
 import ch.srg.mediaplayer.segment.model.Segment;
@@ -489,7 +489,7 @@ public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig
         trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
         eventLogger = new EventLogger(trackSelector);
         DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
-        //&& Util.SDK_INT >= 18
+        UnsupportedDrmException unsupportedDrm = null;
         if (drmConfig != null) {
             try {
                 UUID drmType = drmConfig.getDrmType();
@@ -499,13 +499,11 @@ public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig
                         drmCallback, null, mainHandler, this);
                 setViewType(ViewType.TYPE_SURFACEVIEW);
             } catch (UnsupportedDrmException e) {
-                Event event = Event.buildErrorEvent(this, e);
-                fatalError = event.exception;
-                postEventInternal(event);
+                fatalError = new SRGDrmMediaPlayerException(e);
             }
         }
-        DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this.context, drmSessionManager, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER);
 
+        DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this.context, drmSessionManager, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER);
         exoPlayer = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector, new DefaultLoadControl());
         exoPlayer.addListener(this);
         exoPlayer.setVideoListener(this);
@@ -517,6 +515,9 @@ public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig
 
         audioFocusChangeListener = new OnAudioFocusChangeListener(new WeakReference<>(this));
         audioFocusGranted = false;
+        if (fatalError != null) {
+            Event event = new Event(this, Event.Type.UNSUPPORTED_DRM, (SRGMediaPlayerException) fatalError);
+        }
     }
 
     private synchronized void startBackgroundThreadIfNecessary() {

From 95fe49a1c5fa60ab10af9d690b7fddf7b9ec747b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Mon, 23 Jul 2018 17:34:32 +0200
Subject: [PATCH 09/23] Fix depreciated warning

---
 .../src/main/java/ch/srg/mediaplayer/EventLogger.java           | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/EventLogger.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/EventLogger.java
index 3a8638e..0254184 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/EventLogger.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/EventLogger.java
@@ -59,7 +59,7 @@
 /**
  * Logs player events using {@link Log}.
  */
-/* package */ final class EventLogger implements ExoPlayer.EventListener,
+/* package */ final class EventLogger implements Player.EventListener,
         AudioRendererEventListener, VideoRendererEventListener, AdaptiveMediaSourceEventListener,
         ExtractorMediaSource.EventListener, DefaultDrmSessionManager.EventListener,
         MetadataRenderer.Output {

From 67f2fb0ff12e5ecc14dd4284736d45951911c82c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Mon, 23 Jul 2018 17:34:49 +0200
Subject: [PATCH 10/23] Fix import

---
 .../src/main/java/ch/srg/mediaplayer/EventLogger.java           | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/EventLogger.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/EventLogger.java
index 0254184..bab0ffd 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/EventLogger.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/EventLogger.java
@@ -20,12 +20,12 @@
 import android.util.Log;
 import android.view.Surface;
 
-import com.akamai.android.exoplayer2loader.AkamaiExoPlayerLoader;
 import com.google.android.exoplayer2.C;
 import com.google.android.exoplayer2.ExoPlaybackException;
 import com.google.android.exoplayer2.ExoPlayer;
 import com.google.android.exoplayer2.Format;
 import com.google.android.exoplayer2.PlaybackParameters;
+import com.google.android.exoplayer2.Player;
 import com.google.android.exoplayer2.RendererCapabilities;
 import com.google.android.exoplayer2.Timeline;
 import com.google.android.exoplayer2.audio.AudioRendererEventListener;

From d6e89dfe6472c4f2526731f5c445b0968f8c1915 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Tue, 24 Jul 2018 09:11:58 +0200
Subject: [PATCH 11/23] Add Drm error reporting to akamai qos

---
 .../main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index 430f5dd..5cfc677 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -2060,6 +2060,9 @@ public void onDrmKeysLoaded() {
     public void onDrmSessionManagerError(Exception e) {
         eventLogger.onDrmSessionManagerError(e);
         postFatalErrorInternal(new SRGDrmMediaPlayerException(e));
+        if (akamaiExoPlayerLoader != null) {
+            akamaiExoPlayerLoader.onDrmSessionManagerError(e);
+        }
     }
 
     @Override

From 9b459eb2ef8bca3ea1e9d1d55d4ede9bd9ff9a33 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Tue, 24 Jul 2018 10:38:36 +0200
Subject: [PATCH 12/23] Add DRM Error to the event and Make DRM exception more
 priority other playback exceptions

---
 .../ch/srg/mediaplayer/SRGMediaPlayerController.java | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index 5cfc677..f787060 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -225,6 +225,7 @@ public enum Type {
             STATE_CHANGE,
             FATAL_ERROR,
             TRANSIENT_ERROR, /* To be removed ? */
+            DRM_ERROR,
 
             MEDIA_READY_TO_PLAY,
             MEDIA_COMPLETED,
@@ -499,7 +500,7 @@ public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig
                         drmCallback, null, mainHandler, this);
                 setViewType(ViewType.TYPE_SURFACEVIEW);
             } catch (UnsupportedDrmException e) {
-                fatalError = new SRGDrmMediaPlayerException(e);
+                fatalError = e;
             }
         }
 
@@ -1494,10 +1495,19 @@ private void broadcastEvent(Event event) {
     }
 
     private void postFatalErrorInternal(SRGMediaPlayerException e) {
+        // Don't update fatal error if a Drm exception occurs
+        if (fatalError instanceof SRGDrmMediaPlayerException || fatalError instanceof UnsupportedDrmException) {
+            return;
+        }
         this.fatalError = e;
         postEventInternal(Event.buildErrorEvent(this, true, e));
     }
 
+    private void postDrmErrorInternal(SRGDrmMediaPlayerException e) {
+        this.fatalError = e;
+        postEventInternal(new Event(this, Event.Type.DRM_ERROR, e));
+    }
+
     private void postEventInternal(Event.Type eventType) {
         postEventInternal(Event.buildEvent(this, eventType));
     }

From 9a6430f129efaff126562da2770fe740a97019cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Tue, 24 Jul 2018 11:40:04 +0200
Subject: [PATCH 13/23] Add constructor with no DRMConfig

---
 .../srg/mediaplayer/SRGMediaPlayerController.java  | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index f787060..5640259 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -466,12 +466,24 @@ public interface Listener {
     private DrmConfig drmConfig;
 
     /**
-     * Create a new SRGMediaPlayerController with the current context, a mediaPlayerDataProvider, and a TAG
+     * Create a new SRGMediaPlayerController with no DRM support with the current context, a mediaPlayerDataProvider, and a TAG
      * if you need to retrieve a controller
      *
      * @param context context
      * @param tag     tag to identify this controller
      */
+    public SRGMediaPlayerController(Context context, String tag) {
+        this(context, tag, null);
+    }
+
+    /**
+     * Create a new SRGMediaPlayerController with the current context, a mediaPlayerDataProvider, and a TAG
+     * if you need to retrieve a controller
+     *
+     * @param context   context
+     * @param tag       tag to identify this controller
+     * @param drmConfig drm configuration
+     */
     public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig drmConfig) {
         this.context = context;
         this.mainHandler = new Handler(Looper.getMainLooper(), this);

From 9b7d66bee5a92214b95e0c8941f8e6e63072f560 Mon Sep 17 00:00:00 2001
From: sebastien <sebastien.chauvin@gmail.com>
Date: Wed, 25 Jul 2018 12:48:23 +0200
Subject: [PATCH 14/23] Simplify fatal error: drm handled like generic fatal
 errors except that they cannot be superseded by a player error

---
 .../mediaplayer/SRGMediaPlayerController.java | 60 +++++++++----------
 1 file changed, 29 insertions(+), 31 deletions(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index 5640259..8002071 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -163,7 +163,7 @@ public static String getVersion() {
     private static final int MSG_SET_MUTE = 7;
     private static final int MSG_APPLY_STATE = 8;
     private static final int MSG_RELEASE = 9;
-    private static final int MSG_EXCEPTION = 12;
+    private static final int MSG_PLAYER_EXCEPTION = 12;
     private static final int MSG_REGISTER_EVENT_LISTENER = 13;
     private static final int MSG_UNREGISTER_EVENT_LISTENER = 14;
     private static final int MSG_PLAYER_PREPARING = 101;
@@ -225,7 +225,6 @@ public enum Type {
             STATE_CHANGE,
             FATAL_ERROR,
             TRANSIENT_ERROR, /* To be removed ? */
-            DRM_ERROR,
 
             MEDIA_READY_TO_PLAY,
             MEDIA_COMPLETED,
@@ -276,9 +275,7 @@ public enum Type {
             /**
              * The Segment list has changed.
              */
-            SEGMENT_LIST_CHANGE,
-
-            UNSUPPORTED_DRM
+            SEGMENT_LIST_CHANGE
         }
 
         public final Type type;
@@ -426,7 +423,8 @@ public interface Listener {
     private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
     private AudioCapabilities audioCapabilities;
     private EventLogger eventLogger;
-    private ViewType viewType;
+    @NonNull
+    private ViewType viewType = ViewType.TYPE_TEXTUREVIEW;
     private View renderingView;
     private Integer playbackState;
     private List<Segment> segments = new ArrayList<>();
@@ -482,7 +480,7 @@ public SRGMediaPlayerController(Context context, String tag) {
      *
      * @param context   context
      * @param tag       tag to identify this controller
-     * @param drmConfig drm configuration
+     * @param drmConfig drm configuration null for no DRM support
      */
     public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig drmConfig) {
         this.context = context;
@@ -510,7 +508,7 @@ public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig
                 drmSessionManager = new DefaultDrmSessionManager<>(drmType,
                         FrameworkMediaDrm.newInstance(drmType),
                         drmCallback, null, mainHandler, this);
-                setViewType(ViewType.TYPE_SURFACEVIEW);
+                viewType = ViewType.TYPE_SURFACEVIEW;
             } catch (UnsupportedDrmException e) {
                 fatalError = e;
             }
@@ -528,9 +526,6 @@ public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig
 
         audioFocusChangeListener = new OnAudioFocusChangeListener(new WeakReference<>(this));
         audioFocusGranted = false;
-        if (fatalError != null) {
-            Event event = new Event(this, Event.Type.UNSUPPORTED_DRM, (SRGMediaPlayerException) fatalError);
-        }
     }
 
     private synchronized void startBackgroundThreadIfNecessary() {
@@ -771,7 +766,7 @@ public boolean handleMessage(final Message msg) {
                     prepareInternal(uri, data.streamType);
                 } catch (SRGMediaPlayerException e) {
                     logE("onUriLoaded", e);
-                    handleFatalExceptionInternal(e);
+                    handlePlayerExceptionInternal(e);
                 }
                 return true;
 
@@ -815,8 +810,8 @@ public boolean handleMessage(final Message msg) {
                 releaseInternal();
                 return true;
 
-            case MSG_EXCEPTION:
-                handleFatalExceptionInternal((SRGMediaPlayerException) msg.obj);
+            case MSG_PLAYER_EXCEPTION:
+                handlePlayerExceptionInternal((SRGMediaPlayerException) msg.obj);
                 return true;
 
             case MSG_REGISTER_EVENT_LISTENER:
@@ -1178,9 +1173,9 @@ public State getState() {
         return state;
     }
 
-    private void handleFatalExceptionInternal(SRGMediaPlayerException e) {
+    private void handlePlayerExceptionInternal(SRGMediaPlayerException e) {
         logE("exception occurred", e);
-        postFatalErrorInternal(e);
+        postFatalErrorInternal(e, false);
         releaseInternal();
     }
 
@@ -1357,8 +1352,10 @@ private void createRenderingViewInMainThread(final Context parentContext) {
             public void run() {
                 if (viewType == ViewType.TYPE_SURFACEVIEW) {
                     renderingView = new SurfaceView(parentContext);
-                } else {
+                } else if (viewType == ViewType.TYPE_TEXTUREVIEW) {
                     renderingView = new TextureView(parentContext);
+                } else {
+                    throw new IllegalStateException("Unsupported view type: " + viewType);
                 }
                 if (mediaPlayerView != null) {
                     Log.v(TAG, "binding, setVideoRenderingView " + mediaPlayerView);
@@ -1432,7 +1429,15 @@ public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
         });
     }
 
-    public void setViewType(ViewType viewType) {
+    /**
+     * Warning texture view not supported to play DRM content.
+     *
+     * @param viewType view type
+     */
+    public void setViewType(@NonNull ViewType viewType) {
+        if (debugMode && drmConfig != null && viewType == ViewType.TYPE_TEXTUREVIEW) {
+            Log.w(TAG, "Texture view does not support DRM");
+        }
         this.viewType = viewType;
     }
 
@@ -1506,18 +1511,11 @@ private void broadcastEvent(Event event) {
         sendMessage(MSG_FIRE_EVENT, event);
     }
 
-    private void postFatalErrorInternal(SRGMediaPlayerException e) {
-        // Don't update fatal error if a Drm exception occurs
-        if (fatalError instanceof SRGDrmMediaPlayerException || fatalError instanceof UnsupportedDrmException) {
-            return;
+    private void postFatalErrorInternal(SRGMediaPlayerException e, boolean override) {
+        if (override || fatalError != null) {
+            this.fatalError = e;
+            postEventInternal(Event.buildErrorEvent(this, true, e));
         }
-        this.fatalError = e;
-        postEventInternal(Event.buildErrorEvent(this, true, e));
-    }
-
-    private void postDrmErrorInternal(SRGDrmMediaPlayerException e) {
-        this.fatalError = e;
-        postEventInternal(new Event(this, Event.Type.DRM_ERROR, e));
     }
 
     private void postEventInternal(Event.Type eventType) {
@@ -2036,7 +2034,7 @@ public void onRepeatModeChanged(int repeatMode) {
     @Override
     public void onPlayerError(ExoPlaybackException error) {
         manageKeepScreenOnInternal();
-        sendMessage(MSG_EXCEPTION, new SRGMediaPlayerException(error));
+        sendMessage(MSG_PLAYER_EXCEPTION, new SRGMediaPlayerException(error));
     }
 
     @Override
@@ -2081,7 +2079,7 @@ public void onDrmKeysLoaded() {
     @Override
     public void onDrmSessionManagerError(Exception e) {
         eventLogger.onDrmSessionManagerError(e);
-        postFatalErrorInternal(new SRGDrmMediaPlayerException(e));
+        postFatalErrorInternal(new SRGDrmMediaPlayerException(e), true);
         if (akamaiExoPlayerLoader != null) {
             akamaiExoPlayerLoader.onDrmSessionManagerError(e);
         }

From e34cff014b09724d2fe42e8670864d73ecde96b5 Mon Sep 17 00:00:00 2001
From: sebastien <sebastien.chauvin@gmail.com>
Date: Wed, 25 Jul 2018 12:48:32 +0200
Subject: [PATCH 15/23] DRM requires API level 18

---
 .../java/ch/srg/mediaplayer/SRGMediaPlayerController.java  | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index 8002071..f42a8e1 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -60,6 +60,7 @@
 import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
 import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
 import com.google.android.exoplayer2.upstream.FileDataSourceFactory;
+import com.google.android.exoplayer2.util.Util;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -501,7 +502,7 @@ public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig
         eventLogger = new EventLogger(trackSelector);
         DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
         UnsupportedDrmException unsupportedDrm = null;
-        if (drmConfig != null) {
+        if (drmConfig != null && Util.SDK_INT >= 18) {
             try {
                 UUID drmType = drmConfig.getDrmType();
                 HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(drmConfig.getLicenceUrl(), new DefaultHttpDataSourceFactory(userAgent));
@@ -2105,4 +2106,8 @@ public void onDrmKeysRemoved() {
     public void setAkamaiMediaAnalyticsConfiguration(@Nullable AkamaiMediaAnalyticsConfiguration akamaiMediaAnalyticsConfiguration) {
         this.akamaiMediaAnalyticsConfiguration = akamaiMediaAnalyticsConfiguration;
     }
+
+    public static boolean isDrmSupported() {
+        return Util.SDK_INT >= 18;
+    }
 }
\ No newline at end of file

From 24e24642df010d958b109ccc14bbf7b993a5031d Mon Sep 17 00:00:00 2001
From: sebastien <sebastien.chauvin@gmail.com>
Date: Wed, 25 Jul 2018 13:02:38 +0200
Subject: [PATCH 16/23] Fix fatal errors

---
 .../main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index f42a8e1..230ddd1 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -1513,7 +1513,7 @@ private void broadcastEvent(Event event) {
     }
 
     private void postFatalErrorInternal(SRGMediaPlayerException e, boolean override) {
-        if (override || fatalError != null) {
+        if (override || fatalError == null) {
             this.fatalError = e;
             postEventInternal(Event.buildErrorEvent(this, true, e));
         }

From e473b038aa5077e4129126c1724b8a1ef2226edd Mon Sep 17 00:00:00 2001
From: sebastien <sebastien.chauvin@gmail.com>
Date: Wed, 25 Jul 2018 13:03:22 +0200
Subject: [PATCH 17/23] Expose HTTP 403 errors as forbidden errors to show
 different error message

---
 .../mediaplayer/SRGMediaPlayerController.java | 10 +++++++++-
 .../SRGMediaPlayerForbiddenException.java     | 19 +++++++++++++++++++
 2 files changed, 28 insertions(+), 1 deletion(-)
 create mode 100644 srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerForbiddenException.java

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index 230ddd1..5a50f89 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -60,6 +60,7 @@
 import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
 import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
 import com.google.android.exoplayer2.upstream.FileDataSourceFactory;
+import com.google.android.exoplayer2.upstream.HttpDataSource;
 import com.google.android.exoplayer2.util.Util;
 
 import java.lang.annotation.Retention;
@@ -2035,7 +2036,14 @@ public void onRepeatModeChanged(int repeatMode) {
     @Override
     public void onPlayerError(ExoPlaybackException error) {
         manageKeepScreenOnInternal();
-        sendMessage(MSG_PLAYER_EXCEPTION, new SRGMediaPlayerException(error));
+        Throwable cause = error.getCause();
+        SRGMediaPlayerException exception = new SRGMediaPlayerException(error);
+        if (cause instanceof HttpDataSource.InvalidResponseCodeException) {
+            if (((HttpDataSource.InvalidResponseCodeException) cause).responseCode == 403) {
+                exception = new SRGMediaPlayerForbiddenException(error);
+            }
+        }
+        sendMessage(MSG_PLAYER_EXCEPTION, exception);
     }
 
     @Override
diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerForbiddenException.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerForbiddenException.java
new file mode 100644
index 0000000..80fae5e
--- /dev/null
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerForbiddenException.java
@@ -0,0 +1,19 @@
+package ch.srg.mediaplayer;
+
+/**
+ * Created by Axel on 02/03/2015.
+ */
+public class SRGMediaPlayerForbiddenException extends SRGMediaPlayerException {
+
+	public SRGMediaPlayerForbiddenException(String detailMessage) {
+		super(detailMessage);
+	}
+
+	public SRGMediaPlayerForbiddenException(String detailMessage, Throwable throwable) {
+		super(detailMessage, throwable);
+	}
+
+	public SRGMediaPlayerForbiddenException(Throwable throwable) {
+		super(throwable);
+	}
+}

From 42aad506688546b3883ebf5d25480833b912a616 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Wed, 25 Jul 2018 16:45:11 +0200
Subject: [PATCH 18/23] Remove work arround for index out of bound exception

---
 .../main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java   | 1 +
 1 file changed, 1 insertion(+)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index 5a50f89..ccf47d2 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -46,6 +46,7 @@
 import com.google.android.exoplayer2.source.TrackGroup;
 import com.google.android.exoplayer2.source.TrackGroupArray;
 import com.google.android.exoplayer2.source.dash.DashMediaSource;
+import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
 import com.google.android.exoplayer2.source.hls.HlsMediaSource;
 import com.google.android.exoplayer2.text.Cue;
 import com.google.android.exoplayer2.text.TextRenderer;

From 9e1842105c88848e021f5477e78f1aafcb8e033d Mon Sep 17 00:00:00 2001
From: sebastien <sebastien.chauvin@gmail.com>
Date: Wed, 15 Aug 2018 11:46:43 +0200
Subject: [PATCH 19/23] Support playback starting with a user selected segment

---
 .../mediaplayer/SRGMediaPlayerController.java | 46 ++++++++++++++++---
 1 file changed, 40 insertions(+), 6 deletions(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index ccf47d2..8b70e90 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -613,22 +613,50 @@ public boolean play(@NonNull Uri uri, Long startPositionMs, @SRGStreamType int s
      * The corresponding events are triggered when the video loading start and is ready.
      *
      * @param uri             uri of the media
-     * @param startPositionMs start position in milliseconds or null to prevent seek
+     * @param startPositionMs start position in milliseconds relative to uri or segment when given or null to prevent seek
      * @param streamType      {@link SRGMediaPlayerController#STREAM_DASH}, {@link SRGMediaPlayerController#STREAM_HLS}, {@link SRGMediaPlayerController#STREAM_HTTP_PROGRESSIVE} or {@link SRGMediaPlayerController#STREAM_LOCAL_FILE}
-     * @throws SRGMediaPlayerException player exception
+     * @param segments        logical segment list
+     * @param segment         segment to play, must be in segments list. This is considered a user selected segment (SEGMENT_SELECTED is sent)
+     * @throws IllegalArgumentException if segment is not in segment list or uri is null
      */
     @SuppressWarnings("ConstantConditions")
     public void prepare(@NonNull Uri uri,
                         Long startPositionMs,
                         @SRGStreamType int streamType,
-                        List<Segment> segments) throws SRGMediaPlayerException {
+                        List<Segment> segments,
+                        Segment segment) {
         if (uri == null) {
             throw new IllegalArgumentException("Invalid argument: null uri");
         }
-        PrepareUriData data = new PrepareUriData(uri, startPositionMs, streamType, segments);
+        if (segment != null && !segments.contains(segment)) {
+            throw new IllegalArgumentException("Unknown segment: " + segment);
+        }
+        PrepareUriData data = new PrepareUriData(uri, startPositionMs, streamType, segments, segment);
         sendMessage(MSG_PREPARE_FOR_URI, data);
     }
 
+    /**
+     * Try to play a video with a url and corresponding segments, you can't replay the current playing video.
+     * will throw an exception if you haven't setup a data provider or if the media is not present
+     * in the provider.
+     * <p/>
+     * The corresponding events are triggered when the video loading start and is ready.
+     *
+     * @param uri             uri of the media
+     * @param startPositionMs start position in milliseconds or null to prevent seek
+     * @param streamType      {@link SRGMediaPlayerController#STREAM_DASH}, {@link SRGMediaPlayerController#STREAM_HLS}, {@link SRGMediaPlayerController#STREAM_HTTP_PROGRESSIVE} or {@link SRGMediaPlayerController#STREAM_LOCAL_FILE}
+     * @param segments        logical segment list
+     * @throws IllegalArgumentException if segment is not in segment list or uri is null
+     * @ player exception
+     */
+    @SuppressWarnings("ConstantConditions")
+    public void prepare(@NonNull Uri uri,
+                        Long startPositionMs,
+                        @SRGStreamType int streamType,
+                        List<Segment> segments) {
+        prepare(uri, startPositionMs, streamType, segments, null);
+    }
+
     public void keepScreenOn(boolean lock) {
         externalWakeLock = lock;
         manageKeepScreenOnInternal();
@@ -639,12 +667,14 @@ class PrepareUriData {
         Long position;
         int streamType;
         private List<Segment> segments;
+        private Segment segment;
 
-        PrepareUriData(Uri uri, Long position, int streamType, List<Segment> segments) {
+        PrepareUriData(Uri uri, Long position, int streamType, List<Segment> segments, Segment segment) {
             this.uri = uri;
             this.position = position;
             this.streamType = streamType;
             this.segments = segments;
+            this.segment = segment;
         }
 
         @Override
@@ -754,11 +784,15 @@ public boolean handleMessage(final Message msg) {
                 setStateInternal(State.PREPARING);
                 PrepareUriData data = (PrepareUriData) msg.obj;
                 Uri uri = data.uri;
-                this.segments.clear();
                 seekToWhenReady = data.position;
+                this.segments.clear();
                 currentSegment = null;
                 if (data.segments != null) {
                     segments.addAll(data.segments);
+                    if (data.segment != null) {
+                        postEventInternal(new Event(this, Event.Type.SEGMENT_SELECTED, null, data.segment));
+                        seekToWhenReady = data.position + data.segment.getMarkIn();
+                    }
                 }
                 postEventInternal(Event.Type.SEGMENT_LIST_CHANGE);
                 postEventInternal(Event.Type.MEDIA_READY_TO_PLAY);

From 3c84e605c006fe7c8a9a623343654100a77c78cd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Thu, 16 Aug 2018 11:47:12 +0200
Subject: [PATCH 20/23] Use update dash manifest workaround to avoid
 application crash when there is a problem with the manifest

---
 .../java/ch/srg/mediaplayer/DefaultDashChunkSource.java     | 2 ++
 .../java/ch/srg/mediaplayer/SRGMediaPlayerController.java   | 6 +++---
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/DefaultDashChunkSource.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/DefaultDashChunkSource.java
index c7cd217..5bb7ff0 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/DefaultDashChunkSource.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/DefaultDashChunkSource.java
@@ -56,6 +56,7 @@
 import java.util.List;
 
 /**
+ * Work around to avoid application crash due to an OutOfBoundException
  * A default {@link DashChunkSource} implementation.
  */
 public class DefaultDashChunkSource implements DashChunkSource {
@@ -154,6 +155,7 @@ public void updateManifest(DashManifest newManifest, int newPeriodIndex) {
             List<Representation> representations = getRepresentations();
             for (int i = 0; i < representationHolders.length; i++) {
                 int index = trackSelection.getIndexInTrackGroup(i);
+                // FIXME : workaround, check if still needed with 2.8.2
                 if (index >= representations.size()) {
                     index = 0;
                     Log.e("DefaultDashChunkSource", "invalid track index " + index + " (" + representations.size() + ") ");
diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index 8b70e90..bc29429 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -46,7 +46,6 @@
 import com.google.android.exoplayer2.source.TrackGroup;
 import com.google.android.exoplayer2.source.TrackGroupArray;
 import com.google.android.exoplayer2.source.dash.DashMediaSource;
-import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
 import com.google.android.exoplayer2.source.hls.HlsMediaSource;
 import com.google.android.exoplayer2.text.Cue;
 import com.google.android.exoplayer2.text.TextRenderer;
@@ -791,7 +790,7 @@ public boolean handleMessage(final Message msg) {
                     segments.addAll(data.segments);
                     if (data.segment != null) {
                         postEventInternal(new Event(this, Event.Type.SEGMENT_SELECTED, null, data.segment));
-                        seekToWhenReady = data.position + data.segment.getMarkIn();
+                        seekToWhenReady = (data.position != null ? data.position : 0) + data.segment.getMarkIn();
                     }
                 }
                 postEventInternal(Event.Type.SEGMENT_LIST_CHANGE);
@@ -959,8 +958,9 @@ private void prepareInternal(@NonNull Uri videoUri, int streamType) throws SRGMe
 
             switch (streamType) {
                 case STREAM_DASH:
+                    // Use DefaultDashChunkSource with workaround that don't crash the application if problem during manifest parsing
                     mediaSource = new DashMediaSource(videoUri, new DefaultHttpDataSourceFactory(userAgent),
-                            new DefaultDashChunkSource.Factory(dataSourceFactory), mainHandler, eventLogger);
+                            new ch.srg.mediaplayer.DefaultDashChunkSource.Factory(dataSourceFactory), mainHandler, eventLogger);
                     break;
                 case STREAM_HLS:
                     mediaSource = new HlsMediaSource(videoUri, dataSourceFactory, mainHandler, eventLogger);

From e8325b2b7e3632988c4441bba1988cc149e12a64 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Thu, 16 Aug 2018 12:04:01 +0200
Subject: [PATCH 21/23] Fix log message showing the bad index number

---
 .../main/java/ch/srg/mediaplayer/DefaultDashChunkSource.java    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/DefaultDashChunkSource.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/DefaultDashChunkSource.java
index 5bb7ff0..a0f18f2 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/DefaultDashChunkSource.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/DefaultDashChunkSource.java
@@ -157,8 +157,8 @@ public void updateManifest(DashManifest newManifest, int newPeriodIndex) {
                 int index = trackSelection.getIndexInTrackGroup(i);
                 // FIXME : workaround, check if still needed with 2.8.2
                 if (index >= representations.size()) {
-                    index = 0;
                     Log.e("DefaultDashChunkSource", "invalid track index " + index + " (" + representations.size() + ") ");
+                    index = 0;
                 }
                 Representation representation = representations.get(index);
                 representationHolders[i].updateRepresentation(periodDurationUs, representation);

From 6354c3b9933d0a394a6aa5e636927efee4d4a9ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Fri, 17 Aug 2018 09:12:24 +0200
Subject: [PATCH 22/23] Documentation exoplayer github issue

---
 .../main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java   | 1 +
 1 file changed, 1 insertion(+)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index bc29429..317b93b 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -959,6 +959,7 @@ private void prepareInternal(@NonNull Uri videoUri, int streamType) throws SRGMe
             switch (streamType) {
                 case STREAM_DASH:
                     // Use DefaultDashChunkSource with workaround that don't crash the application if problem during manifest parsing
+                    // https://github.com/google/ExoPlayer/issues/2795
                     mediaSource = new DashMediaSource(videoUri, new DefaultHttpDataSourceFactory(userAgent),
                             new ch.srg.mediaplayer.DefaultDashChunkSource.Factory(dataSourceFactory), mainHandler, eventLogger);
                     break;

From ca788e08a3abd4d49efaa16a2f6a494ca205c49d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= <joaquim.stahli@rts.ch>
Date: Mon, 20 Aug 2018 14:59:28 +0200
Subject: [PATCH 23/23] Fix #160 glitch when play media at position

---
 .../mediaplayer/SRGMediaPlayerController.java   | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
index 317b93b..c386213 100644
--- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
+++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java
@@ -783,14 +783,14 @@ public boolean handleMessage(final Message msg) {
                 setStateInternal(State.PREPARING);
                 PrepareUriData data = (PrepareUriData) msg.obj;
                 Uri uri = data.uri;
-                seekToWhenReady = data.position;
+                Long playbackStartPosition = data.position;
                 this.segments.clear();
                 currentSegment = null;
                 if (data.segments != null) {
                     segments.addAll(data.segments);
                     if (data.segment != null) {
                         postEventInternal(new Event(this, Event.Type.SEGMENT_SELECTED, null, data.segment));
-                        seekToWhenReady = (data.position != null ? data.position : 0) + data.segment.getMarkIn();
+                        playbackStartPosition = (data.position != null ? data.position : 0) + data.segment.getMarkIn();
                     }
                 }
                 postEventInternal(Event.Type.SEGMENT_LIST_CHANGE);
@@ -799,7 +799,7 @@ public boolean handleMessage(final Message msg) {
                     if (mediaPlayerView != null) {
                         internalUpdateMediaPlayerViewBound();
                     }
-                    prepareInternal(uri, data.streamType);
+                    prepareInternal(uri, playbackStartPosition, data.streamType);
                 } catch (SRGMediaPlayerException e) {
                     logE("onUriLoaded", e);
                     handlePlayerExceptionInternal(e);
@@ -932,7 +932,7 @@ public void run() {
         }
     }
 
-    private void prepareInternal(@NonNull Uri videoUri, int streamType) throws SRGMediaPlayerException {
+    private void prepareInternal(@NonNull Uri videoUri, @Nullable Long playbackStartPosition, int streamType) throws SRGMediaPlayerException {
         Log.v(TAG, "Preparing " + videoUri + " (" + streamType + ")");
         setupAkamaiQos(videoUri);
         try {
@@ -980,6 +980,15 @@ private void prepareInternal(@NonNull Uri videoUri, int streamType) throws SRGMe
             }
 
             exoPlayer.prepare(mediaSource);
+            if (playbackStartPosition != null) {
+                try {
+                    exoPlayer.seekTo(playbackStartPosition);
+                    checkSegmentChange(playbackStartPosition); // Done here ?
+                } catch (IllegalStateException ignored) {
+                    Log.w(TAG, "Invalid initial playback position", ignored);
+                }
+            }
+
         } catch (Exception e) {
             release();
             throw new SRGMediaPlayerException(e);