diff --git a/Kyoo.Transcoder/src/Transcoder.cpp b/Kyoo.Transcoder/src/Transcoder.cpp index 210619f8..8f4945ac 100644 --- a/Kyoo.Transcoder/src/Transcoder.cpp +++ b/Kyoo.Transcoder/src/Transcoder.cpp @@ -18,6 +18,7 @@ int transmux(const char *path, const char *out_path, float *playable_duration) int *stream_map; int stream_count; int ret = 0; + std::string seg_path = ((std::string)out_path).substr(0, strrchr(out_path, '/') - out_path).append("/segments/"); *playable_duration = 0; if (open_input_context(&in_ctx, path) != 0) @@ -54,10 +55,10 @@ int transmux(const char *path, const char *out_path, float *playable_duration) } av_dump_format(out_ctx, 0, out_path, true); - - std::filesystem::create_directory(((std::string)out_path).substr(0, strrchr(out_path, '/') - out_path).append("/dash/")); - av_dict_set(&options, "init_seg_name", "dash/init-stream$RepresentationID$.m4s", 0); - av_dict_set(&options, "media_seg_name", "dash/chunk-stream$RepresentationID$-$Number%05d$.m4s", 0); + std::filesystem::create_directory(seg_path); + av_dict_set(&options, "hls_segment_filename", seg_path.append("%v-%03d.ts").c_str(), 0); + av_dict_set(&options, "hls_base_url", "segment/", 0); + av_dict_set(&options, "hls_list_size", "0", 0); av_dict_set(&options, "streaming", "1", 0); if (open_output_file_for_write(out_ctx, out_path, &options) != 0) diff --git a/Kyoo/ClientApp/angular.json b/Kyoo/ClientApp/angular.json index 4be75a64..50b7f79e 100644 --- a/Kyoo/ClientApp/angular.json +++ b/Kyoo/ClientApp/angular.json @@ -32,7 +32,7 @@ "scripts": [ "./node_modules/jquery/dist/jquery.min.js", "./node_modules/bootstrap/dist/js/bootstrap.bundle.min.js", - "./node_modules/dashjs/dist/dash.all.min.js", + "./node_modules/hls.js/dist/hls.js", "./src/libraries/subtitles.js" ] }, diff --git a/Kyoo/ClientApp/package-lock.json b/Kyoo/ClientApp/package-lock.json index 5d978d81..bdd34efa 100644 --- a/Kyoo/ClientApp/package-lock.json +++ b/Kyoo/ClientApp/package-lock.json @@ -1270,6 +1270,12 @@ "@types/node": "*" } }, + "@types/hls.js": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/hls.js/-/hls.js-0.12.5.tgz", + "integrity": "sha512-UWNROsxKxW1yASacUZzvjGZwS1xGp1gN3Yyd6XWMpqr8HLiYepVWZLxsyfPNsgSJrQ1oyz03tV7y4dS5nlSOaw==", + "dev": true + }, "@types/jasmine": { "version": "3.3.16", "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.3.16.tgz", @@ -2754,11 +2760,6 @@ } } }, - "codem-isoboxer": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/codem-isoboxer/-/codem-isoboxer-0.3.6.tgz", - "integrity": "sha512-LuO8/7LW6XuR5ERn1yavXAfodGRhuY2yP60JTZIw5yNYMCE5lUVbk3NFUCJxjnphQH+Xemp5hOGb1LgUXm00Xw==" - }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -3146,16 +3147,6 @@ "assert-plus": "^1.0.0" } }, - "dashjs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/dashjs/-/dashjs-3.0.0.tgz", - "integrity": "sha512-XyVkjeHB4mqzf/Y7ARQ1IqbRBaee0osAulwCFV5ZNZ734wea8LbSC/23zKI32BulW7Kk6f7I6onW1WDMHRgz7Q==", - "requires": { - "codem-isoboxer": "0.3.6", - "fast-deep-equal": "2.0.1", - "imsc": "^1.0.2" - } - }, "date-format": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", @@ -3999,7 +3990,8 @@ "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true }, "fast-json-stable-stringify": { "version": "2.0.0", @@ -4555,6 +4547,22 @@ "minimalistic-assert": "^1.0.1" } }, + "hls.js": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-0.12.4.tgz", + "integrity": "sha512-e8OPxQ60dBVsdkv4atdxR21KzC1mgwspM41qpozpj3Uv1Fz4CaeQy3FWoaV2O+QKKbNRvV5hW+/LipCWdrwnMQ==", + "requires": { + "eventemitter3": "3.1.0", + "url-toolkit": "^2.1.6" + }, + "dependencies": { + "eventemitter3": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==" + } + } + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -4816,21 +4824,6 @@ "resolve-cwd": "^2.0.0" } }, - "imsc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/imsc/-/imsc-1.1.0.tgz", - "integrity": "sha512-z2aUE3X00O39fCgkLHEVbfKG/D9cBrmsbf4NjP7K1gQb06YBjgljq5nZD72HYMFG2lRJI7QY7v+JVxg6o6I/Jg==", - "requires": { - "sax": "1.2.1" - }, - "dependencies": { - "sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", - "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" - } - } - }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -10220,6 +10213,11 @@ "requires-port": "^1.0.0" } }, + "url-toolkit": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.1.6.tgz", + "integrity": "sha512-UaZ2+50am4HwrV2crR/JAf63Q4VvPYphe63WGeoJxeu8gmOm0qxPt+KsukfakPNrX9aymGNEkkaoICwn+OuvBw==" + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", diff --git a/Kyoo/ClientApp/package.json b/Kyoo/ClientApp/package.json index a04cd18a..49aec6af 100644 --- a/Kyoo/ClientApp/package.json +++ b/Kyoo/ClientApp/package.json @@ -22,9 +22,9 @@ "@angular/platform-browser-dynamic": "~8.2.0", "@angular/router": "~8.2.0", "bootstrap": "^4.3.1", - "dashjs": "^3.0.0", "detect-browser": "^4.8.0", "hammerjs": "^2.0.8", + "hls.js": "^0.12.4", "jquery": "^3.4.1", "popper.js": "^1.15.0", "zone.js": "~0.9.1" @@ -35,6 +35,7 @@ "@angular/compiler-cli": "~8.2.0", "@angular/language-service": "~8.2.0", "@types/bootstrap": "^4.3.1", + "@types/hls.js": "^0.12.5", "@types/jasmine": "~3.3.8", "@types/jasminewd2": "~2.0.3", "@types/jquery": "^3.3.31", diff --git a/Kyoo/ClientApp/src/app/player/player.component.ts b/Kyoo/ClientApp/src/app/player/player.component.ts index 350eee02..2fcffa90 100644 --- a/Kyoo/ClientApp/src/app/player/player.component.ts +++ b/Kyoo/ClientApp/src/app/player/player.component.ts @@ -4,7 +4,7 @@ import { DomSanitizer, Title } from "@angular/platform-browser"; import { ActivatedRoute, Event, NavigationCancel, NavigationEnd, NavigationStart, Router } from "@angular/router"; import { Track, WatchItem } from "../../models/watch-item"; import { Location } from "@angular/common"; -import { MediaPlayer } from "dashjs"; +import * as Hls from "hls.js" import { getPlaybackMethod, method } from "../../videoSupport/playbackMethodDetector"; declare var SubtitleManager: any; @@ -43,8 +43,7 @@ export class PlayerComponent implements OnInit playMethod: method; private player: HTMLVideoElement; - private dashPlayer: dashjs.MediaPlayerClass = MediaPlayer().create(); - private dashPlayerInitialized: boolean = false; + private hlsPlayer: Hls = new Hls(); private thumb: HTMLElement; private progress: HTMLElement; private buffered: HTMLElement; @@ -381,22 +380,27 @@ export class PlayerComponent implements OnInit selectPlayMethod() { - if (this.dashPlayerInitialized) - this.dashPlayer.reset(); if (this.playMethod == method.direct) { this.player.src = "/video/" + this.item.link; - this.dashPlayerInitialized = false; } else if (this.playMethod == method.transmux) { - this.dashPlayer.initialize(this.player, "/video/transmux/" + this.item.link + "/", true); - this.dashPlayerInitialized = true; + this.hlsPlayer.loadSource("/video/transmux/" + this.item.link + "/"); + this.hlsPlayer.attachMedia(this.player); + this.hlsPlayer.on(Hls.Events.MANIFEST_LOADED, () => + { + this.player.play(); + }); } else { - this.dashPlayer.initialize(this.player, "/video/transcode/" + this.item.link + "/", true); - this.dashPlayerInitialized = true; + this.hlsPlayer.loadSource("/video/transcode/" + this.item.link + "/"); + this.hlsPlayer.attachMedia(this.player); + this.hlsPlayer.on(Hls.Events.MANIFEST_LOADED, () => + { + this.player.play(); + }); } } diff --git a/Kyoo/Controllers/VideoController.cs b/Kyoo/Controllers/VideoController.cs index b1a55ce5..78d2d551 100644 --- a/Kyoo/Controllers/VideoController.cs +++ b/Kyoo/Controllers/VideoController.cs @@ -33,7 +33,7 @@ namespace Kyoo.Controllers return NotFound(); } - [HttpGet("transmux/{showSlug}-s{seasonNumber}e{episodeNumber}/")] + [HttpGet("transmux/{showSlug}-s{seasonNumber}e{episodeNumber}")] public async Task Transmux(string showSlug, long seasonNumber, long episodeNumber) { WatchItem episode = libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber); @@ -42,7 +42,7 @@ namespace Kyoo.Controllers { string path = await transcoder.Transmux(episode); if (path != null) - return PhysicalFile(path, "application/dash+xml", true); + return PhysicalFile(path, "application/x-mpegURL ", true); else return StatusCode(500); } @@ -50,13 +50,13 @@ namespace Kyoo.Controllers return NotFound(); } - [HttpGet("transmux/{episodeLink}/dash/{chunk}")] + [HttpGet("transmux/{episodeLink}/segment/{chunk}")] public IActionResult GetTransmuxedChunk(string episodeLink, string chunk) { string path = Path.Combine(transmuxPath, episodeLink); - path = Path.Combine(path, "dash" + Path.DirectorySeparatorChar + chunk); + path = Path.Combine(path, "segments" + Path.DirectorySeparatorChar + chunk); - return PhysicalFile(path, "video/iso.segment"); + return PhysicalFile(path, "video/MP2T"); } [HttpGet("transcode/{showSlug}-s{seasonNumber}e{episodeNumber}")] diff --git a/Kyoo/InternalAPI/Transcoder/Transcoder.cs b/Kyoo/InternalAPI/Transcoder/Transcoder.cs index 97511f6d..55308362 100644 --- a/Kyoo/InternalAPI/Transcoder/Transcoder.cs +++ b/Kyoo/InternalAPI/Transcoder/Transcoder.cs @@ -43,7 +43,7 @@ namespace Kyoo.InternalAPI public async Task Transmux(WatchItem episode) { string folder = Path.Combine(transmuxPath, episode.Link); - string manifest = Path.Combine(folder, episode.Link + ".mpd"); + string manifest = Path.Combine(folder, episode.Link + ".m3u8"); float playableDuration = 0; bool transmuxFailed = false; diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 908f5709..172afca7 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -58,7 +58,7 @@ namespace Kyoo ctx.Response.Headers.Remove("X-Powered-By"); ctx.Response.Headers.Remove("Server"); ctx.Response.Headers.Add("Feature-Policy", "autoplay 'self'; fullscreen"); - ctx.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"); + ctx.Response.Headers.Add("Content-Security-Policy", "default-src 'self' data: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:; style-src 'self' 'unsafe-inline'"); ctx.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); ctx.Response.Headers.Add("Referrer-Policy", "no-referrer"); ctx.Response.Headers.Add("Access-Control-Allow-Origin", "null");