Search code examples
javaandroidflutterhttp-live-streamingm3u8

Cannot find sync byte m3u8 on ExoPlayer2


I have a AES 128bit encrypted m3u8 playlist. I tried to host this on,

  1. Google Cloud Storage Bucket via Cloudflare
  2. Local Xampp server

The playlist works on HTML5 web player. Then I tried to play the m3u8 file in a Android app. I tried, A Flutter app, A React Native app and a native Java app

I have tried almost all the HLS libraries available for Flutter and React Native. But at the end every players shows the same error Regarding Google ExoPlayer. I'm trying to fix this for almost a month now. I have checked most of the Github issues but no luck.

This is the error I see ( Copied from the Flutter terminal but same error shows for RN and native java app, too)

Restarted application in 4,802ms.
I/ExoPlayerImpl(25099): Release 2354449 [ExoPlayerLib/2.13.1] [m21, SM-M215F, samsung, 30] [goog.exo.core, goog.exo.hls]
I/ExoPlayerImpl(25099): Init 91918d7 [ExoPlayerLib/2.13.1] [m21, SM-M215F, samsung, 30]
6
I/System.out(25099): (HTTPLog)-Static: isSBSettingEnabled false
E/ExoPlayerImplInternal(25099): Playback error
E/ExoPlayerImplInternal(25099):   com.google.android.exoplayer2.ExoPlaybackException: Source error
E/ExoPlayerImplInternal(25099):       at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:579)
E/ExoPlayerImplInternal(25099):       at android.os.Handler.dispatchMessage(Handler.java:102)
E/ExoPlayerImplInternal(25099):       at android.os.Looper.loop(Looper.java:246)
E/ExoPlayerImplInternal(25099):       at android.os.HandlerThread.run(HandlerThread.java:67)
E/ExoPlayerImplInternal(25099):   Caused by: com.google.android.exoplayer2.ParserException: Cannot find sync byte. Most likely not a Transport Stream.
E/ExoPlayerImplInternal(25099):       at com.google.android.exoplayer2.extractor.ts.TsExtractor.findEndOfFirstTsPacketInBuffer(TsExtractor.java:453)
E/ExoPlayerImplInternal(25099):       at com.google.android.exoplayer2.extractor.ts.TsExtractor.read(TsExtractor.java:320)
E/ExoPlayerImplInternal(25099):       at com.google.android.exoplayer2.source.hls.BundledHlsMediaChunkExtractor.read(BundledHlsMediaChunkExtractor.java:67)
E/ExoPlayerImplInternal(25099):       at com.google.android.exoplayer2.source.hls.HlsMediaChunk.feedDataToExtractor(HlsMediaChunk.java:434)
E/ExoPlayerImplInternal(25099):       at com.google.android.exoplayer2.source.hls.HlsMediaChunk.loadMedia(HlsMediaChunk.java:404)
E/ExoPlayerImplInternal(25099):       at com.google.android.exoplayer2.source.hls.HlsMediaChunk.load(HlsMediaChunk.java:355)
E/ExoPlayerImplInternal(25099):       at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:415)
E/ExoPlayerImplInternal(25099):       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
E/ExoPlayerImplInternal(25099):       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
E/ExoPlayerImplInternal(25099):       at java.lang.Thread.run(Thread.java:923)
E/flutter (25099): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: PlatformException(VideoError, Video player had error com.google.android.exoplayer2.ExoPlaybackException: Source error, null, null)
E/flutter (25099):

I'll also add the content of the m3u8 file,

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:12
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="http://192.168.1.2/key/video.key",IV=0x00000000000000000000000000000000
#EXTINF:10.666667,
playlist0.ts
#EXTINF:11.666667,
playlist1.ts
#EXT-X-ENDLIST

I see that the issue is with TS files since the error message show the error in TsExtractor.java file. Here I also tried to see the HTTP response headers of one of the TS file with curl,

C:\Users\mdils>curl -D - http://localhost/key/playlist0.ts
HTTP/1.1 200 OK
Date: Fri, 23 Apr 2021 16:01:24 GMT
Server: Apache/2.4.46 (Win64) OpenSSL/1.1.1g PHP/7.4.11
Last-Modified: Sun, 28 Feb 2021 15:29:09 GMT
ETag: "239f80-5bc6729a3ba75"
Accept-Ranges: bytes
Content-Length: 2334592
Access-Control-Allow-Origin: *

Any help regarding this really appreciate.

UPDATE

Sample playlist - https://drive.google.com/drive/folders/1Q6MJNy5HT-wlMqAUmpFBvmHaCumW73Xz?usp=sharing

The FFMPEG command used for encode

ffmpeg -i input.mp4 -c copy -bsf:v h264_mp4toannexb -hls_list_size 0 -hls_time 10 -hls_key_info_file key_info.txt playback.m3u8

UPDATE - 2021-04-25

Here is a working m3u8 file, ( Copied from ExoPlayer demo app )

#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:9.97667,    
fileSequence0.ts
#EXTINF:9.97667,    
fileSequence1.ts
#EXTINF:9.97667,
... 

Comparing this m3u8 file with the above one, the only difference is the file with the error has a aes-128 bit encrypted playlist.

Then, when I check the source code, I found this method on ExoPlayer source code,

  /**
   * Returns the position of the end of the first TS packet (exclusive) in the packet buffer.
   *
   * <p>This may be a position beyond the buffer limit if the packet has not been read fully into
   * the buffer, or if no packet could be found within the buffer.
   */
  private int findEndOfFirstTsPacketInBuffer() throws ParserException {
    int searchStart = tsPacketBuffer.getPosition();
    int limit = tsPacketBuffer.limit();
    int syncBytePosition =
        TsUtil.findSyncBytePosition(tsPacketBuffer.getData(), searchStart, limit);
    // Discard all bytes before the sync byte.
    // If sync byte is not found, this means discard the whole buffer.
    tsPacketBuffer.setPosition(syncBytePosition);
    int endOfPacket = syncBytePosition + TS_PACKET_SIZE;
    if (endOfPacket > limit) {
      bytesSinceLastSync += syncBytePosition - searchStart;
      if (mode == MODE_HLS && bytesSinceLastSync > TS_PACKET_SIZE * 2) {
        throw new ParserException("Cannot find sync byte. Most likely not a Transport Stream.");
      }
    } else {
      // We have found a packet within the buffer.
      bytesSinceLastSync = 0;
    }
    return endOfPacket;
  } 

According to the comments of the above function, the above error message is thrown when it can't find the sync byte. So the only thing I can assume is maybe the player failed to decrypted the first TS file with the provided key ? ( The key is correct as this works on HLS web players )


Solution

  • Your key file is invalid and you get garbage when decrypting the TS segments. The FFmpeg documentation for hls_key_info_file says:

    The key file is read as a single packed array of 16 octets in binary format

    Your key file has 32 bytes. If you take the first 16 bytes of your current key file and output them in binary it will decrypt correctly. Example:

    xxd -p -l 16 video.key | xxd -r -p - video_bin.key

    Use video_bin.key in your playlist instead. Of course it would be better to generate a valid key in the first place.