Can not stream an encrypted hls video
ExoPlayer Version
2.17.1
Devices that reproduce the issue
NA
Devices that do not reproduce the issue
NA
Reproducible in the demo app?
Not tested
Reproduction steps
I can not provide the url since it will expire in a few minutes, but I can provide the final .ts file tmp.
When I debug it, it seems the HlsMediaChunk.java:495 will execute
long bytesToRead = dataSource.open(dataSpec); , which will finally call Aes128dataSource.open(),
and it seems the only available return value for this funtion is return C.LENGTH_UNSET;
so that bytesToRead will be -1 and the streaming procedure can not process.
Please tell me where is the mistake, thanks
Expected result
NA
Actual result
NA
Media
NA
Bug Report
- [ ] You will email the zip file produced by
adb bugreportto [email protected] after filing this issue.
Please share a playlist and a bug-report obtained after the issue occurs, otherwise it's very hard to assist.
Thanks
Please share a playlist and a bug-report obtained after the issue occurs, otherwise it's very hard to assist.
Thanks
Hi, thanks for help.
The problem is, as I mentioned, the video will expire in about 2 minutes, and is geo-restricted to Japan.
This is the crash log
Crash log
com.google.android.exoplayer2.ExoPlaybackException: Source error
at com.google.android.exoplayer2.ExoPlayerImplInternal.handleIoException(ExoPlayerImplInternal.java:641)
at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:611)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:223)
at android.os.HandlerThread.run(HandlerThread.java:67)
Caused by: com.google.android.exoplayer2.ParserException: Cannot find sync byte. Most likely not a Transport Stream.
at com.google.android.exoplayer2.extractor.ts.TsExtractor.findEndOfFirstTsPacketInBuffer(TsExtractor.java:460)
at com.google.android.exoplayer2.extractor.ts.TsExtractor.read(TsExtractor.java:327)
at com.google.android.exoplayer2.source.hls.BundledHlsMediaChunkExtractor.read(BundledHlsMediaChunkExtractor.java:67)
at com.google.android.exoplayer2.source.hls.HlsMediaChunk.feedDataToExtractor(HlsMediaChunk.java:473)
at com.google.android.exoplayer2.source.hls.HlsMediaChunk.loadMedia(HlsMediaChunk.java:437)
at com.google.android.exoplayer2.source.hls.HlsMediaChunk.load(HlsMediaChunk.java:394)
at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:412)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
Hope this helped. If you need any additional information, please reply me.
In the issue description you mention Aes128dataSource, so I assume HLS segments are encrypted with AES-128. My starting point of investigation would be to see if the HLS playlist is formed correctly and that the player parses the encryption information correctly to decrypt the segments. So we need to start at least with the playlist.
We cannot assist further without having some testing data. We can get access to geo-restricted content via vpn.
In the issue description you mention
Aes128dataSource, so I assume HLS segments are encrypted with AES-128. My starting point of investigation would be to see if the HLS playlist is formed correctly and that the player parses the encryption information correctly to decrypt the segments. So we need to start at least with the playlist.We cannot assist further without having some testing data. We can get access to geo-restricted content via vpn.
Hi.
It is a relatively complicated procedure, so I will post my code. Jsoup and nanoJson are used.
First you need to get the download url. The function is provided below, and the input url should be https://www.nicovideo.jp/watch/so35380413
public String getNicoUrl(String url){
final Map<String, List<String>> headers = new HashMap<>();
headers.put("Content-Type", Collections.singletonList("application/json"));
DownloaderImpl downloader = DownloaderImpl.getInstance(); //a simple okhttp downloader and can be replaced
Response response;
try {
response = downloader.get(String.valueOf(url), null, NiconicoService.LOCALE); // NiconicoService.LOCALE = Localization.fromLocalizationCode("ja-JP")
final Document page = Jsoup.parse(response.responseBody());
JsonObject watch = JsonParser.object().from(
page.getElementById("js-initial-watch-data").attr("data-api-data"));
final JsonObject session
= watch.getObject("media").getObject("delivery").getObject("movie");
final JsonObject encryption = watch.getObject("media").getObject("delivery").getObject("encryption");
final String s = NiconicoDMCPayloadBuilder.buildJSON(session.getObject("session"), encryption);
response = downloader.post("https://api.dmc.nico/api/sessions?_format=json", headers, s.getBytes(StandardCharsets.UTF_8), NiconicoService.LOCALE);
final JsonObject content = JsonParser.object().from(response.responseBody());
final String contentURL = content.getObject("data").getObject("session")
.getString("content_uri");
return String.valueOf(Uri.parse(contentURL));
} catch (ReCaptchaException | JsonParserException | IOException e) {
e.printStackTrace();
}
return null;
}
// NiconicoDMCPayloadBuilder.buildJSON
public static String buildJSON(final JsonObject obj, final JsonObject encryption) throws JsonParserException {
JsonStringWriter temp = JsonWriter.string()
.object()
.object("session")
.value("recipe_id", obj.getString("recipeId"))
.value("content_id", obj.getString("contentId"))
.value("content_type", "movie")
.array("content_src_id_sets")
.object()
.array("content_src_ids")
.object()
.object("src_id_to_mux")
.array("video_src_ids", obj.getArray("videos"))
.array("audio_src_ids", obj.getArray("audios"))
.end()
.end()
.end()
.end()
.end()
.value("timing_constraint", "unlimited")
.object("keep_method")
.object("heartbeat")
.value("lifetime", obj.getLong("heartbeatLifetime"))
.end()
.end()
.object("protocol")
.value("name", "http")
.object("parameters")
.object("http_parameters")
.object("parameters")
.object(obj.getArray("protocols").getString(0).equals("hls") ? "hls_parameters" : "http_output_download_parameters")
.value("use_well_known_port", "yes")
.value("use_ssl", "yes")
.value("transfer_preset", "")
.value("segment_duration", 6000);
JsonObject parsedToken = JsonParser.object().from(obj.getString("token"));
if(parsedToken.containsKey("hls_encryption") && encryption != null){
temp = temp.object("encryption")
.object(parsedToken.getString("hls_encryption"))
.value("encrypted_key", encryption.getString("encryptedKey"))
.value("key_uri", encryption.getString("keyUri"))
.end().end();
}
return temp
.end()
.end()
.end()
.end()
.end()
.value("content_uri", "")
.object("session_operation_auth")
.object("session_operation_auth_by_signature")
.value("token", obj.getString("token"))
.value("signature", obj.getString("signature"))
.end()
.end()
.object("content_auth")
.value("auth_type",
obj.getObject("authTypes").getString(obj.getArray("protocols")
.getString(0)))
.value("content_key_timeout", obj.getLong("contentKeyTimeout"))
.value("service_id", "nicovideo")
.value("service_user_id", obj.getString("serviceUserId"))
.end()
.object("client_info")
.value("player_id", obj.getString("playerId"))
.end()
.value("priority", obj.getDouble("priority"))
.end()
.end()
.done();
}
After obtain the download url, you need to configure the header to avoid being blocked.
.setDefaultRequestProperties(Map.of("Referer", "https://www.nicovideo.jp/",
"Origin", "https://www.nicovideo.jp",
"X-Frontend-ID", "6",
"X-Frontend-Version", "0",
"X-Niconico-Language", "en-us"
)))
Hope these help.
Hi. Any updates here? If more info is needed, please feel free to ask me.
Hi, I know it is a torture to debug with such long codes. To process it, I would like to debug myself and provide you the result, and you can just tell me how to do it. Best regards.
Hi! I made some further investigation. It turns out that exoplayer successfully getting all contents(i.e. master.m3u8, playlist.m3u8, license, 1.ts). But after getting the ts file, it fails to be parsed. I have uploaded all the 4 files there, and please help me check it. Thanks! @christosts
any updates?
Could you please check this? @christosts Or maybe I should open a new issue?
We don't need it anymore. Will close it.