Customize & Debug ExoPlayer @droidkaigi 2020

Customize & Debug ExoPlayer @droidkaigi 2020

A374f41eab3f73c50d8bab0652cb207a?s=128

TakuSemba

March 17, 2020
Tweet

Transcript

  1. None
  2. @takusemba https://github.com/TakuSemba

  3. Media Streaming

  4. None
  5. None
  6. None
  7. None
  8. None
  9. Streaming Protocol

  10. Streaming Protocol

  11. None
  12. None
  13. None
  14. None
  15. None
  16. None
  17. None
  18. None
  19. None
  20. None
  21. ...

  22. Adaptive Bitrate

  23. None
  24. None
  25. None
  26. None
  27. None
  28. None
  29. None
  30. 10s ~

  31. ~ 25s

  32. val trackSelectionFactory = AdaptiveTrackSelection.Factory( MIN_DURATION_FOR_QUALITY_INCREASE_MS, MAX_DURATION_FOR_QUALITY_DECREASE_MS, ... )

  33. val trackSelectionFactory = AdaptiveTrackSelection.Factory( MIN_DURATION_FOR_QUALITY_INCREASE_MS, MAX_DURATION_FOR_QUALITY_DECREASE_MS, ... ) AdaptiveTrackSelection.Factory( MIN_DURATION_FOR_QUALITY_INCREASE_MS,

    )
  34. val trackSelectionFactory = AdaptiveTrackSelection.Factory( MIN_DURATION_FOR_QUALITY_INCREASE_MS, MAX_DURATION_FOR_QUALITY_DECREASE_MS, ... ) AdaptiveTrackSelection.Factory( MAX_DURATION_FOR_QUALITY_DECREASE_MS,

    )
  35. val trackSelectionFactory = AdaptiveTrackSelection.Factory( MIN_DURATION_FOR_QUALITY_INCREASE_MS, MAX_DURATION_FOR_QUALITY_DECREASE_MS, BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE, ... ) AdaptiveTrackSelection.Factory(

    BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
  36. val trackSelectionFactory = AdaptiveTrackSelection.Factory( MIN_DURATION_FOR_QUALITY_INCREASE_MS, MAX_DURATION_FOR_QUALITY_DECREASE_MS, BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE, ... ) AdaptiveTrackSelection.Factory(

    BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE, Edge Current Position Buffered Position 0.75
  37. val bandwidthMeter = DefaultBandwidthMeter.Builder(context).build()

  38. Bandwidth Time 10Mbps

  39. Bandwidth Time 10Mbps

  40. Bandwidth Time 10Mbps

  41. Bandwidth Time 10Mbps

  42. Bandwidth Time 10Mbps

  43. Bandwidth Time 10Mbps

  44. Bandwidth Time 10Mbps

  45. Bandwidth Time 10Mbps

  46. Bandwidth Time 10Mbps

  47. Bandwidth Time 10Mbps

  48. Bandwidth Time 10Mbps 6Mbps

  49. Bandwidth Time 10Mbps 6Mbps SlidingWindowMaxWeight

  50. Time 6Mbps SlidingWindowMaxWeight

  51. Time SlidingWindowMaxWeight 6Mbps * 0.75 = 4.5Mbps

  52. Time 6Mbps * 0.75 = 4.5Mbps SlidingWindowMaxWeight BandwidthFraction

  53. val trackSelectionFactory = AdaptiveTrackSelection.Factory( MIN_DURATION_FOR_QUALITY_INCREASE_MS, MAX_DURATION_FOR_QUALITY_DECREASE_MS, BANDWIDTH_FRACTION ... ) val

    bandwidthMeter = DefaultBandwidthMeter.Builder(context) .setSlidingWindowMaxWeight(SLIDING_WINDOW_MAX_WEIGHT) .build()
  54. val trackSelectionFactory = AdaptiveTrackSelection.Factory( MIN_DURATION_FOR_QUALITY_INCREASE_MS, MAX_DURATION_FOR_QUALITY_DECREASE_MS, BANDWIDTH_FRACTION ... ) AdaptiveTrackSelection.Factory(

    BANDWIDTH_FRACTION val bandwidthMeter = DefaultBandwidthMeter.Builder(context) .setSlidingWindowMaxWeight(SLIDING_WINDOW_MAX_WEIGHT) .build()
  55. val trackSelectionFactory = AdaptiveTrackSelection.Factory( MIN_DURATION_FOR_QUALITY_INCREASE_MS, MAX_DURATION_FOR_QUALITY_DECREASE_MS, BANDWIDTH_FRACTION ... ) val

    bandwidthMeter = DefaultBandwidthMeter.Builder(context) .setSlidingWindowMaxWeight(SLIDING_WINDOW_MAX_WEIGHT) .build() .setSlidingWindowMaxWeight(SLIDING_WINDOW_MAX_WEIGHT)
  56. Initial Bitrate

  57. None
  58. ???

  59. 5.7Mbps Wifi

  60. 5.7Mbps Wifi

  61. 2.2Mbps 3G

  62. 2.2Mbps 3G

  63. 2.0Mbps Wifi

  64. 2.0Mbps Wifi

  65. 5.7Mbps Wifi

  66. 5.7Mbps Wifi

  67. val bandwidthMeter = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(INITIAL_BITRATE_ESTIMATE) .build()

  68. val bandwidthMeter = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(INITIAL_BITRATE_ESTIMATE) .build() .setInitialBitrateEstimate(INITIAL_BITRATE_ESTIMATE)

  69. val bandwidthMeter = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .build() 180p 360p 720p Session

    Scope
  70. Session Scope 720p val bandwidthMeter = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .build()

  71. Session Scope 180p 360p 720p val bandwidthMeter = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE)

    .build() 720p
  72. Session Scope val bandwidthMeter = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .build() 180p 360p

    720p
  73. Session Scope val bandwidthMeter = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .build() 720p

  74. Session Scope val bandwidthMeter = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .build() 180p 360p

    720p 720p
  75. fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if (singletonInstance == null) {

    singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .build() } return singletonInstance } Application Scope 180p 360p 720p
  76. Application Scope 720p fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if (singletonInstance

    == null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .build() } return singletonInstance }
  77. Application Scope 720p 720p 720p fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter {

    if (singletonInstance == null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .build() } return singletonInstance } 720p
  78. Application Scope fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if (singletonInstance ==

    null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .build() } return singletonInstance } 720p 720p 720p
  79. Application Scope fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if (singletonInstance ==

    null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .build() } return singletonInstance } 720p Launch App
  80. Application Scope Launch App fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if

    (singletonInstance == null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .build() } return singletonInstance } 720p 180p 360p 720p
  81. Application Scope fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if (singletonInstance ==

    null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .build() } return singletonInstance } 180p 360p 720p
  82. Application Scope fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if (singletonInstance ==

    null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .build() } return singletonInstance } 720p
  83. Application Scope Wifi -> 4G fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter {

    if (singletonInstance == null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .build() } return singletonInstance } 720p 720p
  84. Application Scope fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if (singletonInstance ==

    null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .setResetOnNetworkTypeChange(true) .build() } return singletonInstance } 720p 720p Wifi -> 4G
  85. Application Scope fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if (singletonInstance ==

    null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .setResetOnNetworkTypeChange(true) .build() } return singletonInstance } 720p Wifi -> 4G .setResetOnNetworkTypeChange(true) 720p
  86. Application Scope Wifi -> 4G 720p 180p 360p fun getSingletonBandwidthMeter(context:

    Context): DefaultBandwidthMeter { if (singletonInstance == null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(LOWEST_RESOLUTION_BITRATE) .setResetOnNetworkTypeChange(true) .build() } return singletonInstance } 720p
  87. Lifetime Scope 180p 360p 720p fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter {

    if (singletonInstance == null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(prefs.getLastEstimatedBitrate()) .setResetOnNetworkTypeChange(true) .build() } return singletonInstance }
  88. Lifetime Scope fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if (singletonInstance ==

    null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(prefs.getLastEstimatedBitrate()) .setResetOnNetworkTypeChange(true) .build() } return singletonInstance } .setInitialBitrateEstimate(prefs.getLastEstimatedBitrate()) 180p 360p 720p
  89. Lifetime Scope fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if (singletonInstance ==

    null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(prefs.getLastEstimatedBitrate()) .setResetOnNetworkTypeChange(true) .build() } return singletonInstance } .setResetOnNetworkTypeChange(true) 180p 360p 720p
  90. Lifetime Scope fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if (singletonInstance ==

    null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(prefs.getLastEstimatedBitrate()) .setResetOnNetworkTypeChange(true) .build() } return singletonInstance } 180p 360p 720p
  91. Lifetime Scope fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if (singletonInstance ==

    null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(prefs.getLastEstimatedBitrate()) .setResetOnNetworkTypeChange(true) .build() } return singletonInstance } 720p
  92. 720p 720p 720p Lifetime Scope fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter {

    if (singletonInstance == null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(prefs.getLastEstimatedBitrate()) .setResetOnNetworkTypeChange(true) .build() } return singletonInstance } 720p
  93. Lifetime Scope fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if (singletonInstance ==

    null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(prefs.getLastEstimatedBitrate()) .setResetOnNetworkTypeChange(true) .build() } return singletonInstance } 720p 720p 720p
  94. Lifetime Scope fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if (singletonInstance ==

    null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(prefs.getLastEstimatedBitrate()) .setResetOnNetworkTypeChange(true) .build() } return singletonInstance } 720p Launch App
  95. Launch App Lifetime Scope fun getSingletonBandwidthMeter(context: Context): DefaultBandwidthMeter { if

    (singletonInstance == null) { singletonInstance = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(prefs.getLastEstimatedBitrate()) .setResetOnNetworkTypeChange(true) .build() } return singletonInstance } 720p 720p 720p 720p
  96. Limit Bitrate

  97. None
  98. None
  99. val trackSelectionFactory = AdaptiveTrackSelection.Factory(...) val parameter = DefaultTrackSelector.ParametersBuilder(context) .setMaxVideoBitrate(MAX_VIDEO_BITRARE) .build()

    val trackSelector = DefaultTrackSelector(parameter, trackSelectionFactory)
  100. val trackSelectionFactory = AdaptiveTrackSelection.Factory(...) val parameter = DefaultTrackSelector.ParametersBuilder(context) .setMaxVideoBitrate(MAX_VIDEO_BITRARE) .build()

    val trackSelector = DefaultTrackSelector(parameter, trackSelectionFactory) .setMaxVideoBitrate(MAX_VIDEO_BITRARE)
  101. val trackSelectionFactory = AdaptiveTrackSelection.Factory(...) val parameter = DefaultTrackSelector.ParametersBuilder(context) .setMaxVideoSize(640, 360)

    .build() val trackSelector = DefaultTrackSelector(parameter, trackSelectionFactory)
  102. val trackSelectionFactory = AdaptiveTrackSelection.Factory(...) val parameter = DefaultTrackSelector.ParametersBuilder(context) .setMaxVideoSize(640, 360)

    .build() val trackSelector = DefaultTrackSelector(parameter, trackSelectionFactory) .setMaxVideoSize(640, 360)
  103. None
  104. val newParameter = DefaultTrackSelector.ParametersBuilder(context) .setMaxVideoBitrate(NEW_MAX_VIDEO_BITRARE) .build() trackSelector.parameters = newParameter

  105. val newParameter = DefaultTrackSelector.ParametersBuilder(context) .setMaxVideoBitrate(NEW_MAX_VIDEO_BITRARE) .build() trackSelector.parameters = newParameter trackSelector.parameters

    = newParameter
  106. None
  107. None
  108. None
  109. None
  110. None
  111. class LimitTrackSelection(...) : AdaptiveTrackSelection(...) { private var maxVideoBitrate = Int.MAX_VALUE

    fun setMaxVideoBitrate(maxVideoBitrate: Int) { this.maxVideoBitrate = maxVideoBitrate } override fun canSelectFormat( format: Format, trackBitrate: Int, playbackSpeed: Float, effectiveBitrate: Long ): Boolean { return trackBitrate <= maxVideoBitrate && super.canSelectFormat(...) } }
  112. class LimitTrackSelection(...) : AdaptiveTrackSelection(...) { private var maxVideoBitrate = Int.MAX_VALUE

    fun setMaxVideoBitrate(maxVideoBitrate: Int) { this.maxVideoBitrate = maxVideoBitrate } override fun canSelectFormat( format: Format, trackBitrate: Int, playbackSpeed: Float, effectiveBitrate: Long ): Boolean { return trackBitrate <= maxVideoBitrate && super.canSelectFormat(...) } } AdaptiveTrackSelection(...) { override fun canSelectFormat( format: Format, trackBitrate: Int, playbackSpeed: Float, effectiveBitrate: Long ): Boolean }
  113. class LimitTrackSelection(...) : AdaptiveTrackSelection(...) { private var maxVideoBitrate = Int.MAX_VALUE

    fun setMaxVideoBitrate(maxVideoBitrate: Int) { this.maxVideoBitrate = maxVideoBitrate } override fun canSelectFormat( format: Format, trackBitrate: Int, playbackSpeed: Float, effectiveBitrate: Long ): Boolean { return trackBitrate <= maxVideoBitrate && super.canSelectFormat(...) } } private var maxVideoBitrate = Int.MAX_VALUE fun setMaxVideoBitrate(maxVideoBitrate: Int) { this.maxVideoBitrate = maxVideoBitrate } override fun canSelectFormat( { return trackBitrate <= maxVideoBitrate } }
  114. class LimitTrackSelection(...) : AdaptiveTrackSelection(...) { private var maxVideoBitrate = Int.MAX_VALUE

    fun setMaxVideoBitrate(maxVideoBitrate: Int) { this.maxVideoBitrate = maxVideoBitrate } override fun canSelectFormat( format: Format, trackBitrate: Int, playbackSpeed: Float, effectiveBitrate: Long ): Boolean { return trackBitrate <= maxVideoBitrate && super.canSelectFormat(...) } } https://github.com/google/ExoPlayer/issues/2250
  115. Chunkless Preparation

  116. m3u8 Playlist

  117. ts First Chunk Playlist

  118. First Chunk Playlist Prepare Decoder

  119. First Chunk Playlist Prepare Decoder Start Decodeing

  120. #EXTM3U #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=200000,CODECS="mp4a.40.2, avc1.4d4015" index1.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=600000,CODECS=“mp4a.40.2, avc1.4d401e" index2.m3u8 …

  121. #EXTM3U #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=200000,CODECS="mp4a.40.2, avc1.4d4015" index1.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=600000,CODECS=“mp4a.40.2, avc1.4d401e" index2.m3u8 … CODECS="mp4a.40.2, avc1.4d4015"

    CODECS=“mp4a.40.2, avc1.4d401e"
  122. #EXTM3U #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=200000,CODECS="mp4a.40.2, avc1.4d4015" index1.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=600000,CODECS=“mp4a.40.2, avc1.4d401e" index2.m3u8 … CODECS="mp4a.40.2, avc1.4d4015"

    CODECS=“mp4a.40.2, avc1.4d401e" val hlsMediaSource = HlsMediaSource.Factory(dataSourceFactory) .setAllowChunklessPreparation(true) .createMediaSource(uri)
  123. #EXTM3U #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=200000,CODECS="mp4a.40.2, avc1.4d4015" index1.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=600000,CODECS=“mp4a.40.2, avc1.4d401e" index2.m3u8 … CODECS="mp4a.40.2, avc1.4d4015"

    CODECS=“mp4a.40.2, avc1.4d401e" val hlsMediaSource = HlsMediaSource.Factory(dataSourceFactory) .setAllowChunklessPreparation(true) .createMediaSource(uri) .setAllowChunklessPreparation(true)
  124. m3u8 Playlist

  125. ts First Chunk Playlist Prepare Decoder

  126. First Chunk Playlist Prepare Decoder Start Decodeing

  127. Reuse Decoder

  128. Renderer

  129. None
  130. Disabled Enabled Started

  131. Disabled Enabled Started No Streams No Decoders Have Streams Might

    Have Decoders Decoding
  132. Disabled Enabled Started No Streams No Decoders Have Streams Might

    Have Decoders Decoding Prepare Player
  133. Disabled Enabled Started ~ v2.10.0 No Streams No Decoders Have

    Streams Might Have Decoders Decoding Prepare Player
  134. Disabled Enabled Started ~ v2.10.0 No Streams No Decoders Have

    Streams Might Have Decoders Decoding Prepare Player
  135. Disabled Enabled Started ~ v2.10.0

  136. Disabled Enabled Started ~ v2.10.0 v2.10.0 ~ Disabled Enabled Started

  137. v2.10.0 ~ Disabled Enabled Started

  138. v2.10.0 ~ Disabled Enabled Started

  139. v2.10.0 ~ Disabled Enabled Started Prepare Decoder Content A Content

    B Content C
  140. Tunneled Video Playback

  141. Media Source Renderer (Video) Renderer (Audio) Media Codec (Video) Media

    Codec (Audio) Audio Track Surface A/V Sync
  142. Media Source Renderer (Video) Renderer (Audio) Media Codec (Video) Media

    Codec (Audio) Audio Track Surface A/V Sync
  143. Media Source Renderer (Video) Renderer (Audio) Media Codec (Video) Media

    Codec (Audio) Audio Track Surface A/V Sync
  144. Media Source Renderer (Video) Renderer (Audio) Media Codec (Video) Media

    Codec (Audio) Audio Track Surface A/V Sync
  145. Media Source Renderer (Video) Renderer (Audio) Media Codec (Video) Media

    Codec (Audio) Audio Track Surface A/V Sync
  146. Media Source Renderer (Video) Renderer (Audio) Media Codec (Video) Media

    Codec (Audio) Audio Track Surface A/V Sync
  147. val parameter = DefaultTrackSelector.ParametersBuilder() .setTunnelingAudioSessionId(C.generateAudioSessionIdV21(this)) .build()

  148. val parameter = DefaultTrackSelector.ParametersBuilder() .setTunnelingAudioSessionId(C.generateAudioSessionIdV21(this)) .build() .setTunnelingAudioSessionId(C.generateAudioSessionIdV21(this))

  149. Systrace

  150. thread(name = "example-thread") { Trace.beginSection("do something") // do something Trace.endSection()

    }
  151. thread(name = "example-thread") { Trace.beginSection("do something") // do something Trace.endSection()

    } python systrace.py --app package-name --time=10 -o ~/Downloads/example.html
  152. None
  153. python systrace.py --app "com.google.android.exoplayer2.demo" --time=10 -o ~/Downloads/trace.html

  154. Load Master Playlist

  155. Load Media Playlist

  156. Load First Chunk

  157. Prepare Decoder

  158. None
  159. Initialize Video Decoder

  160. Initialize Audio Decoder

  161. None
  162. Render Video Output Buffer

  163. Traditional Preparation Chunkless Preparation

  164. Traditional Preparation Chunkless Preparation

  165. Video Decoder Audio Decoder Decoder Initialization (150ms)

  166. ~ v2.10.0 (reuse decoder) v2.10.0 ~ (create recoders)

  167. ~ v2.10.0 (reuse decoder) v2.10.0 ~ (create recoders)

  168. with tunneling without tunneling

  169. Reference https://medium.com/google-exoplayer/faster-hls-preparation-f6611aa15ea6 https://medium.com/google-exoplayer/tunneled-video-playback-in-exoplayer-84f084a8094d https://medium.com/google-exoplayer/improved-decoder-reuse-in-exoplayer-ef4c6d99591d https://exoplayer.dev