-
-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add dash js * PlyrPlayer: implement dash player, not tested yet * add test dash videos * update mime type thing * add dash to video services * add mpd parser * refactor youtube video duration parsing into a new file * add dash to omniplayer * add dash service adapter * add some unit tests
- Loading branch information
Showing
15 changed files
with
503 additions
and
98 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,54 +1,55 @@ | ||
{ | ||
"name": "ott-client", | ||
"version": "0.8.0", | ||
"license": "AGPL-3.0-or-later", | ||
"type": "module", | ||
"scripts": { | ||
"serve": "vite serve", | ||
"build": "vite build", | ||
"lint": "tsc --noEmit && eslint --ext .js,.ts,.vue --fix .", | ||
"i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\"", | ||
"lint-ci": "tsc --noEmit && eslint .", | ||
"test": "vitest run --coverage", | ||
"cy:open": "cypress open", | ||
"cy:run": "cypress run --component" | ||
}, | ||
"dependencies": { | ||
"@fortawesome/fontawesome-free": "^5.14.1", | ||
"@mdi/font": "^3.9.97", | ||
"@peertube/embed-api": "^0.0.6", | ||
"@vimeo/player": "^2.20.1", | ||
"@vueuse/core": "^9.6.0", | ||
"hls.js": "1.4.8", | ||
"load-script": "^1.0.0", | ||
"material-design-icons-iconfont": "^5.0.1", | ||
"ott-common": "./common", | ||
"plyr": "3.7.8", | ||
"sortablejs": "^1.15.0", | ||
"sortablejs-vue3": "^1.2.3", | ||
"video.js": "^7.15.4", | ||
"vue": "3.2.47", | ||
"vue-axios": "^2.1.5", | ||
"vue-i18n": "9.2.2", | ||
"vue-router": "^4.1.5", | ||
"vue-slider-component": "4.1.0-beta.6", | ||
"vuetify": "3.3.3", | ||
"vuex": "4.1.0" | ||
}, | ||
"devDependencies": { | ||
"@cypress/vue": "^5.0.3", | ||
"@types/video.js": "^7.3.29", | ||
"@types/vimeo__player": "^2.16.0", | ||
"@types/web": "0.0.80", | ||
"@vitejs/plugin-vue": "^4.0.0", | ||
"@vitest/coverage-c8": "^0.25.1", | ||
"@vue/eslint-config-typescript": "^11.0.3", | ||
"@vue/test-utils": "2.2.1", | ||
"eslint-plugin-vue": "9.7.0", | ||
"jsdom": "^21.1.0", | ||
"sass": "^1.41.1", | ||
"vite": "^4.4.2", | ||
"vite-plugin-vuetify": "1.0.2", | ||
"vitest": "^0.25.1" | ||
} | ||
} | ||
"name": "ott-client", | ||
"version": "0.8.0", | ||
"license": "AGPL-3.0-or-later", | ||
"type": "module", | ||
"scripts": { | ||
"serve": "vite serve", | ||
"build": "vite build", | ||
"lint": "tsc --noEmit && eslint --ext .js,.ts,.vue --fix .", | ||
"i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\"", | ||
"lint-ci": "tsc --noEmit && eslint .", | ||
"test": "vitest run --coverage", | ||
"cy:open": "cypress open", | ||
"cy:run": "cypress run --component" | ||
}, | ||
"dependencies": { | ||
"@fortawesome/fontawesome-free": "^5.14.1", | ||
"@mdi/font": "^3.9.97", | ||
"@peertube/embed-api": "^0.0.6", | ||
"@vimeo/player": "^2.20.1", | ||
"@vueuse/core": "^9.6.0", | ||
"dashjs": "4.7.1", | ||
"hls.js": "1.4.8", | ||
"load-script": "^1.0.0", | ||
"material-design-icons-iconfont": "^5.0.1", | ||
"ott-common": "./common", | ||
"plyr": "3.7.8", | ||
"sortablejs": "^1.15.0", | ||
"sortablejs-vue3": "^1.2.3", | ||
"video.js": "^7.15.4", | ||
"vue": "3.2.47", | ||
"vue-axios": "^2.1.5", | ||
"vue-i18n": "9.2.2", | ||
"vue-router": "^4.1.5", | ||
"vue-slider-component": "4.1.0-beta.6", | ||
"vuetify": "3.3.3", | ||
"vuex": "4.1.0" | ||
}, | ||
"devDependencies": { | ||
"@cypress/vue": "^5.0.3", | ||
"@types/video.js": "^7.3.29", | ||
"@types/vimeo__player": "^2.16.0", | ||
"@types/web": "0.0.80", | ||
"@vitejs/plugin-vue": "^4.0.0", | ||
"@vitest/coverage-c8": "^0.25.1", | ||
"@vue/eslint-config-typescript": "^11.0.3", | ||
"@vue/test-utils": "2.2.1", | ||
"eslint-plugin-vue": "9.7.0", | ||
"jsdom": "^21.1.0", | ||
"sass": "^1.41.1", | ||
"vite": "^4.4.2", | ||
"vite-plugin-vuetify": "1.0.2", | ||
"vitest": "^0.25.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import URL from "url"; | ||
import _ from "lodash"; | ||
import { ServiceAdapter } from "../serviceadapter"; | ||
import { | ||
LocalFileException, | ||
UnsupportedMimeTypeException, | ||
UnsupportedVideoType, | ||
} from "../exceptions"; | ||
import { getMimeType, isSupportedMimeType } from "../mime"; | ||
import { getLogger } from "../logger"; | ||
import { Video } from "../../common/models/video"; | ||
import { DashMPD } from "@liveinstantly/dash-mpd-parser"; | ||
import axios from "axios"; | ||
import { parseIso8601Duration } from "./parsing/iso8601"; | ||
|
||
const log = getLogger("dash"); | ||
|
||
export default class DashVideoAdapter extends ServiceAdapter { | ||
get serviceId(): "dash" { | ||
return "dash"; | ||
} | ||
|
||
get isCacheSafe(): boolean { | ||
return false; | ||
} | ||
|
||
isCollectionURL(link: string): boolean { | ||
return false; | ||
} | ||
|
||
getVideoId(link: string): string { | ||
return link; | ||
} | ||
|
||
canHandleURL(link: string): boolean { | ||
const url = URL.parse(link); | ||
return /\/*\.(mpd)$/.test((url.path ?? "/").split("?")[0]); | ||
} | ||
|
||
async fetchVideoInfo(link: string): Promise<Video> { | ||
const url = URL.parse(link); | ||
if (url.protocol === "file:") { | ||
throw new LocalFileException(); | ||
} | ||
const fileName = (url.pathname ?? "").split("/").slice(-1)[0].trim(); | ||
const extension = fileName.split(".").slice(-1)[0]; | ||
const mime = getMimeType(extension) ?? "unknown"; | ||
if (!isSupportedMimeType(mime)) { | ||
throw new UnsupportedMimeTypeException(mime); | ||
} | ||
return await this.handleMpd(url); | ||
} | ||
|
||
async handleMpd(url: URL.UrlWithStringQuery): Promise<Video> { | ||
const resp = await axios.get(url.href); | ||
const mpd = new DashMPD(); | ||
mpd.parse(resp.data); | ||
const manifest = mpd.getJSON(); | ||
|
||
return this.parseMpdManifest(url, manifest); | ||
} | ||
|
||
parseMpdManifest(url: URL.UrlWithStringQuery, manifest: any): Video { | ||
// docs for how the parser works: https://github.com/liveinstantly/dash-mpd-parser | ||
|
||
log.debug(JSON.stringify(manifest)); | ||
|
||
const profiles: string = manifest["MPD"]["@profiles"] ?? ""; | ||
if (profiles.includes("isoff-live")) { | ||
// live streams are not supported right now | ||
// technically, there are VOD streams that use this profile, but im feeling lazy rn | ||
throw new UnsupportedVideoType("livestream"); | ||
} | ||
|
||
const durationRaw: string = manifest["MPD"]["@mediaPresentationDuration"]; | ||
const duration = parseIso8601Duration(durationRaw); | ||
|
||
const title = this.extractTitle(manifest); | ||
|
||
return { | ||
service: this.serviceId, | ||
id: url.href, | ||
title: title ?? url.pathname?.split("/").slice(-1)[0] ?? url.href, | ||
description: `Full Link: ${url.href}`, | ||
mime: "application/dash+xml", | ||
length: duration, | ||
dash_url: url.href, | ||
}; | ||
} | ||
|
||
/** | ||
* Attempts to find a title for the video from the manifest. Returns undefined if no title is found. | ||
* | ||
* Video metadata is not always available in the manifest, and it's not standardized, so this method will probably usually fail. | ||
*/ | ||
extractTitle(manifest: any): string | undefined { | ||
try { | ||
if ("ProgramInformation" in manifest["MPD"]) { | ||
return manifest["MPD"]["ProgramInformation"]["Title"]; | ||
} | ||
|
||
const periods = manifest["MPD"]["Period"]; | ||
for (const period of periods) { | ||
const adaptationSets = period["AdaptationSet"]; | ||
for (const adaptationSet of adaptationSets) { | ||
const representations = adaptationSet["Representation"]; | ||
for (const representation of representations) { | ||
if ("Title" in representation) { | ||
return representation["Title"]; | ||
} | ||
} | ||
} | ||
} | ||
} catch (e) { | ||
log.warn("Error extracting title from manifest", e); | ||
} | ||
|
||
return undefined; | ||
} | ||
} |
Oops, something went wrong.