11package com .google .android .exoplayer2 .source .sabr .parser .processor ;
22
33import android .util .Base64 ;
4+ import android .util .Pair ;
45
56import androidx .annotation .NonNull ;
67
@@ -683,8 +684,8 @@ public int getLiveSegmentTargetDurationSec() {
683684 // .build();
684685 //}
685686
686- public VideoPlaybackAbrRequest buildInitVideoPlaybackAbrRequest (int trackType ) {
687- int resolution = 1080 ; // ???
687+ public VideoPlaybackAbrRequest buildVideoPlaybackAbrRequest (int trackType , boolean isInit ) {
688+ int resolution = trackType == C . TRACK_TYPE_VIDEO ? 720 : 0 ; // current track height (if video)
688689 int bandwidthEstimate = 1350000 ; // ???
689690
690691 ClientAbrState clientAbrStateTest = ClientAbrState .newBuilder ()
@@ -695,14 +696,26 @@ public VideoPlaybackAbrRequest buildInitVideoPlaybackAbrRequest(int trackType) {
695696 .setDrcEnabled (false )
696697 .setSabrForceMaxNetworkInterruptionDurationMs (0 )
697698 .build ();
698-
699+
700+ Pair <List <BufferedRange >, FormatId > bufferRanges = createBufferedRangesAndDiscardFormat (trackType );
701+
702+ List <FormatId > selectedFormats = getSelectedFormatIds (trackType );
703+
704+ if (isInit ) {
705+ selectedFormats .clear ();
706+ }
707+
708+ if (bufferRanges .second != null ) {
709+ selectedFormats .add (0 , bufferRanges .second );
710+ }
711+
699712 return VideoPlaybackAbrRequest .newBuilder ()
700713 .setClientAbrState (clientAbrStateTest )
701714 .addAllPreferredVideoFormatIds (videoFormatSelector .formatIds )
702715 .addAllPreferredAudioFormatIds (audioFormatSelector .formatIds )
703716 .addAllPreferredSubtitleFormatIds (captionFormatSelector .formatIds )
704- .addAllSelectedFormatIds (getAllSelectedFormatIds ( trackType ) )
705- .addAllBufferedRanges (createAllBufferedRanges ( trackType ) )
717+ .addAllSelectedFormatIds (selectedFormats )
718+ .addAllBufferedRanges (bufferRanges . first )
706719 .setVideoPlaybackUstreamerConfig (
707720 ByteString .copyFrom (
708721 Base64 .decode (videoPlaybackUstreamerConfig , Base64 .URL_SAFE )
@@ -734,15 +747,15 @@ private int getAllSelectedTracksBitfield(int trackType) {
734747 return 0 ;
735748 }
736749
737- private List <FormatId > getAllSelectedFormatIds (int trackType ) {
750+ private List <FormatId > getSelectedFormatIds (int trackType ) {
738751 List <FormatId > result = new ArrayList <>();
739752
740753 if (!videoFormatSelector .formatIds .isEmpty () && trackType == C .TRACK_TYPE_VIDEO ) {
741- return videoFormatSelector .formatIds ;
754+ result = videoFormatSelector .formatIds ;
742755 } else if (!audioFormatSelector .formatIds .isEmpty () && trackType == C .TRACK_TYPE_AUDIO ) {
743- return audioFormatSelector .formatIds ;
756+ result = audioFormatSelector .formatIds ;
744757 } else if (!captionFormatSelector .formatIds .isEmpty () && trackType == C .TRACK_TYPE_TEXT ) {
745- return captionFormatSelector .formatIds ;
758+ result = captionFormatSelector .formatIds ;
746759 }
747760
748761 return result ;
@@ -820,30 +833,6 @@ private List<BufferedRange> createBufferedRanges() {
820833 return result ;
821834 }
822835
823- private List <BufferedRange > createAllBufferedRanges (int trackType ) {
824- List <FormatId > allSelectedFormatIds = getAllSelectedFormatIds (trackType );
825-
826- int durationMs = 2147483647 ; // ???
827-
828- TimeRange timeRange = TimeRange .newBuilder ()
829- .setStartTicks (0 )
830- .setDurationTicks (durationMs )
831- .setTimescale (1_000 )
832- .build ();
833- BufferedRange bufferedRange = BufferedRange .newBuilder ()
834- .setFormatId (allSelectedFormatIds .get (0 ))
835- .setStartTimeMs (0 )
836- .setDurationMs (durationMs )
837- .setStartSegmentIndex ((int ) durationMs )
838- .setEndSegmentIndex ((int ) durationMs )
839- .setTimeRange (timeRange )
840- .build ();
841- List <BufferedRange > bufferedRanges = new ArrayList <>();
842- bufferedRanges .add (bufferedRange );
843-
844- return bufferedRanges ;
845- }
846-
847836 private FormatSelector matchFormatSelector (FormatInitializationMetadata formatInitMetadata ) {
848837 for (FormatSelector formatSelector : new FormatSelector []{videoFormatSelector , audioFormatSelector , captionFormatSelector }) {
849838 if (formatSelector == null ) {
@@ -872,4 +861,85 @@ public void setCaptionFormatSelector(CaptionSelector captionFormatSelector) {
872861 this .captionFormatSelector = captionFormatSelector ;
873862 initializeClientAbrState ();
874863 }
864+
865+ /**
866+ * Adds buffering information to the ABR request for all active formats.<br/><br/>
867+ *
868+ * NOTE:
869+ * On the web, mobile, and TV clients, buffered ranges in combination to player time is what dictates the segments you get.
870+ * In our case, we are cheating a bit by abusing the player time field (in clientAbrState), setting it to the exact start
871+ * time value of the segment we want, while YouTube simply uses the actual player time.<br/><br/>
872+ *
873+ * We don't have to fully replicate this behavior for two reasons:
874+ * 1. The SABR server will only send so much segments for a given player time. That means players like Shaka would
875+ * not be able to buffer more than what the server thinks is enough. It would behave like YouTube's.
876+ * 2. We don't have to know what segment a buffered range starts/ends at. It is easy to do in Shaka, but not in other players.
877+ *
878+ * @return The format to discard (if any) - typically formats that are active but not currently requested.
879+ */
880+ private Pair <List <BufferedRange >, FormatId > createBufferedRangesAndDiscardFormat (int trackType ) {
881+ FormatId audioFormat = audioFormatSelector .formatIds .isEmpty () ? null : audioFormatSelector .formatIds .get (0 );
882+ FormatId videoFormat = videoFormatSelector .formatIds .isEmpty () ? null : videoFormatSelector .formatIds .get (0 );
883+
884+ FormatId formatToDiscard = null ;
885+ List <BufferedRange > bufferedRanges = new ArrayList <>();
886+
887+ FormatId currentFormat = trackType == C .TRACK_TYPE_VIDEO ? videoFormat : audioFormat ;
888+ int currentFormatITag = currentFormat != null ? currentFormat .getItag () : -1 ;
889+
890+ for (FormatId activeFormat : new FormatId []{videoFormat , audioFormat }) {
891+ if (activeFormat == null ) {
892+ continue ;
893+ }
894+
895+ boolean shouldDiscard = currentFormatITag != activeFormat .getItag ();
896+ FormatId initializedFormat = null ;
897+
898+ BufferedRange bufferedRange = shouldDiscard ? createFullBufferRange (activeFormat ) : createPartialBufferRange (initializedFormat );
899+
900+ if (bufferedRange != null ) {
901+ bufferedRanges .add (bufferedRange );
902+
903+ if (shouldDiscard ) {
904+ formatToDiscard = activeFormat ;
905+ }
906+ }
907+ }
908+
909+ return new Pair <>(bufferedRanges , formatToDiscard );
910+ }
911+
912+ /**
913+ * Creates a bogus buffered range for a format. Used when we want to signal to the server to not send any
914+ * segments for this format.
915+ * @param format - The format to create a full buffer range for.
916+ * @return A BufferedRange object indicating the entire format is buffered.
917+ */
918+ private BufferedRange createFullBufferRange (@ NonNull FormatId format ) {
919+ return BufferedRange .newBuilder ()
920+ .setFormatId (format )
921+ .setDurationMs (Integer .MAX_VALUE )
922+ .setStartTimeMs (0 )
923+ .setStartSegmentIndex (Integer .MAX_VALUE )
924+ .setEndSegmentIndex (Integer .MAX_VALUE )
925+ .setTimeRange (TimeRange .newBuilder ()
926+ .setDurationTicks (Integer .MAX_VALUE )
927+ .setStartTicks (0 )
928+ .setTimescale (1_000 )
929+ .build ())
930+ .build ();
931+ }
932+
933+ /**
934+ * Creates a buffered range representing a partially buffered format.
935+ * @param initializedFormat - The format with initialization data.
936+ * @return A BufferedRange object with segment information, or null if no metadata is available.
937+ */
938+ private BufferedRange createPartialBufferRange (FormatId initializedFormat ) {
939+ if (initializedFormat == null ) {
940+ return null ;
941+ }
942+
943+ return null ;
944+ }
875945}
0 commit comments