diff --git a/env.d.ts b/env.d.ts
index 11f02fe..95880d3 100644
--- a/env.d.ts
+++ b/env.d.ts
@@ -1 +1,3 @@
///
+
+declare module "libass-wasm";
diff --git a/package-lock.json b/package-lock.json
index e18a509..23e85ec 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,6 +23,7 @@
"hls.js": "^1.5.7",
"less": "^4.1.3",
"less-loader": "^11.1.3",
+ "libass-wasm": "^4.1.0",
"lodash": "^4.17.21",
"mpegts.js": "^1.7.3",
"nprogress": "^0.2.0",
@@ -3962,6 +3963,11 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/libass-wasm": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/libass-wasm/-/libass-wasm-4.1.0.tgz",
+ "integrity": "sha512-+RbYT/uuI6VHExCmGyUuMg3A2gQOaCRTzSn8GGDSf3q4cEoUNiINd9u4RGfZXA1UKafW+Hv8bmcKIX4FKbSh0Q=="
+ },
"node_modules/lie": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
diff --git a/package.json b/package.json
index a749b98..dd3e3fa 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"hls.js": "^1.5.7",
"less": "^4.1.3",
"less-loader": "^11.1.3",
+ "libass-wasm": "^4.1.0",
"lodash": "^4.17.21",
"mpegts.js": "^1.7.3",
"nprogress": "^0.2.0",
diff --git a/src/hooks/useMovie.ts b/src/hooks/useMovie.ts
index 9cef361..b12493a 100644
--- a/src/hooks/useMovie.ts
+++ b/src/hooks/useMovie.ts
@@ -56,12 +56,28 @@ export const useMovieApi = (roomToken: string) => {
headers: { Authorization: roomToken }
});
- if (currentMovie.value) {
- console.log(currentMovie.value);
- room.currentMovie = currentMovie.value.movie;
- room.currentStatus = currentMovie.value.status;
- room.currentExpireId = currentMovie.value.expireId;
+ if (!currentMovie.value) return;
+
+ room.currentMovie = currentMovie.value.movie;
+ room.currentStatus = currentMovie.value.status;
+ room.currentExpireId = currentMovie.value.expireId;
+
+ const url = currentMovie.value.movie.base.url;
+ // when cross origin, add token to headers and query
+ if (url.startsWith(window.location.origin) || url.startsWith("/api/movie")) {
+ room.currentMovie.base.url = url.includes("?")
+ ? `${url}&token=${roomToken}`
+ : `${url}?token=${roomToken}`;
+ }
+
+ const defaultSubtitle = currentMovie.value.movie.base.subtitles;
+ for (let key in defaultSubtitle) {
+ if (defaultSubtitle[key].url.includes("token=")) continue;
+ defaultSubtitle[key].url.includes("?")
+ ? (defaultSubtitle[key].url = `${defaultSubtitle[key].url}&token=${roomToken}`)
+ : (defaultSubtitle[key].url = `${defaultSubtitle[key].url}?token=${roomToken}`);
}
+ room.currentMovie.base.subtitles = defaultSubtitle;
} catch (err: any) {
console.log(err);
ElNotification({
diff --git a/src/plugins/artplayer-plugin-ass/fonts/SourceHanSansCN-Bold.woff2 b/src/plugins/artplayer-plugin-ass/fonts/SourceHanSansCN-Bold.woff2
new file mode 100644
index 0000000..28d1506
Binary files /dev/null and b/src/plugins/artplayer-plugin-ass/fonts/SourceHanSansCN-Bold.woff2 differ
diff --git a/src/plugins/artplayer-plugin-ass/fonts/TimesNewRoman.ttf b/src/plugins/artplayer-plugin-ass/fonts/TimesNewRoman.ttf
new file mode 100644
index 0000000..eaf5e11
Binary files /dev/null and b/src/plugins/artplayer-plugin-ass/fonts/TimesNewRoman.ttf differ
diff --git a/src/plugins/artplayer-plugin-ass/index.d.ts b/src/plugins/artplayer-plugin-ass/index.d.ts
new file mode 100644
index 0000000..4b51618
--- /dev/null
+++ b/src/plugins/artplayer-plugin-ass/index.d.ts
@@ -0,0 +1,12 @@
+import type Artplayer from "artplayer"
+import type SubtitlesOctopus from "libass-wasm"
+import { type Options } from "libass-wasm"
+
+export = artplayerPluginAss
+export as namespace artplayerPluginAss
+type Ass = {
+ name: "artplayerPluginAss"
+ instance: SubtitlesOctopus
+}
+
+declare const artplayerPluginAss: (options: Options) => (art: Artplayer) => Ass
diff --git a/src/plugins/artplayer-plugin-ass/index.js b/src/plugins/artplayer-plugin-ass/index.js
new file mode 100644
index 0000000..a336868
--- /dev/null
+++ b/src/plugins/artplayer-plugin-ass/index.js
@@ -0,0 +1,110 @@
+/**
+ * ref: https://github.com/alist-org/alist-web/blob/main/src/components/artplayer-plugin-ass/index.js
+ */
+import SubtitlesOctopus from "libass-wasm"
+import workerUrl from "libass-wasm/dist/js/subtitles-octopus-worker.js?url"
+import wasmUrl from "libass-wasm/dist/js/subtitles-octopus-worker.wasm?url"
+
+import TimesNewRomanFont from "./fonts/TimesNewRoman.ttf?url"
+import fallbackFont from "./fonts/SourceHanSansCN-Bold.woff2?url"
+
+let instance = null
+
+function isAbsoluteUrl(url) {
+ return /^https?:\/\//.test(url)
+}
+
+function toAbsoluteUrl(url) {
+ if (isAbsoluteUrl(url)) return url
+
+ // handle absolute URL when the `Worker` of `BLOB` type loading network resources
+ return new URL(url, document.baseURI).toString()
+}
+
+function loadWorker({ workerUrl, wasmUrl }) {
+ return new Promise((resolve) => {
+ fetch(workerUrl)
+ .then((res) => res.text())
+ .then((text) => {
+ let workerScriptContent = text
+
+ workerScriptContent = workerScriptContent.replace(
+ /wasmBinaryFile\s*=\s*"(subtitles-octopus-worker\.wasm)"/g,
+ (_match, wasm) => {
+ if (!wasmUrl) {
+ wasmUrl = new URL(wasm, toAbsoluteUrl(workerUrl)).toString()
+ } else {
+ wasmUrl = toAbsoluteUrl(wasmUrl)
+ }
+
+ return `wasmBinaryFile = "${wasmUrl}"`
+ },
+ )
+
+ const workerBlob = new Blob([workerScriptContent], {
+ type: "text/javascript",
+ })
+ resolve(URL.createObjectURL(workerBlob))
+ })
+ })
+}
+
+function setVisible(visible) {
+ if (instance.canvasParent)
+ instance.canvasParent.style.display = visible ? "block" : "none"
+}
+
+function artplayerPluginAss(options) {
+ return async (art) => {
+ instance = new SubtitlesOctopus({
+ // TODO: load available fonts from manage panel
+ availableFonts: {
+ "times new roman": toAbsoluteUrl(TimesNewRomanFont),
+ },
+ workerUrl: await loadWorker({ workerUrl, wasmUrl }),
+ fallbackFont: toAbsoluteUrl(fallbackFont),
+ video: art.template.$video,
+ ...options,
+ })
+
+ instance.canvasParent.className = "artplayer-plugin-ass"
+ instance.canvasParent.style.cssText = `
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ user-select: none;
+ pointer-events: none;
+ z-index: 20;
+ `
+ // switch subtitle track
+ art.on("artplayer-plugin-ass:switch", (subtitle) => {
+ let newSubAddr
+ if (subtitle.startsWith('/api/movie/proxy/')) {
+ newSubAddr = window.location.origin + subtitle
+ } else {
+ newSubAddr = subtitle
+ }
+ instance.freeTrack()
+ instance.setTrackByUrl(newSubAddr)
+ console.log("plugin->切换字幕:", newSubAddr);
+ setVisible(true)
+ })
+
+ // set subtitle visible
+ art.on("subtitle", (visible) => setVisible(visible))
+ art.on("artplayer-plugin-ass:visible", (visible) => setVisible(visible))
+
+ // set subtitle offset
+ art.on("subtitleOffset", (offset) => (instance.timeOffset = offset))
+
+ // when player destory
+ art.on("destroy", () => instance.dispose())
+
+ return {
+ name: "artplayerPluginAss",
+ instance: instance,
+ }
+ }
+}
+
+export default artplayerPluginAss
diff --git a/src/plugins/subtitle.ts b/src/plugins/subtitle.ts
index bba487b..045c2c7 100644
--- a/src/plugins/subtitle.ts
+++ b/src/plugins/subtitle.ts
@@ -1,5 +1,7 @@
import type Artplayer from "artplayer";
import type { ComponentOption } from "artplayer/types/component";
+import type { Events } from "artplayer/types/events";
+import { ElMessage } from "element-plus";
const newSubtitleHtml = (name: string): HTMLElement => {
const SubtitleHtml = document.createElement("span");
@@ -39,10 +41,19 @@ export const newSubtitleControl = (
};
}),
onSelect(this: Artplayer, selector: any) {
- console.log("切换字幕:", selector);
if (selector.html === "关闭") {
this.subtitle.show = false;
+ this.emit("artplayer-plugin-ass:visible" as keyof Events, false);
+ } else if (selector.type.toLowerCase() === "ass") {
+ let newUrl;
+ if (selector.url.startsWith("/api/movie/proxy/")) {
+ newUrl + window.location.origin + selector.url;
+ } else if (!selector.url.startsWith("http")) return ElMessage.error("无效的字幕地址");
+ newUrl = selector.url;
+ this.subtitle.show = false;
+ this.emit("artplayer-plugin-ass:switch" as keyof Events, newUrl);
} else {
+ this.emit("artplayer-plugin-ass:visible" as keyof Events, false);
this.subtitle.switch(selector.url, { type: selector.type });
this.subtitle.show = true;
}
diff --git a/src/views/Cinema.vue b/src/views/Cinema.vue
index aa7bd82..9cf9e5d 100644
--- a/src/views/Cinema.vue
+++ b/src/views/Cinema.vue
@@ -25,6 +25,7 @@ import MovieList from "@/components/cinema/MovieList.vue";
import MoviePush from "@/components/cinema/MoviePush.vue";
import type { Subtitles } from "@/types/Movie";
import { RoomMemberPermission } from "@/types/Room";
+import artplayerPluginAss from "@/plugins/artplayer-plugin-ass";
const Player = defineAsyncComponent(() => import("@/components/Player.vue"));
@@ -139,18 +140,50 @@ const playerOption = computed(() => {
newLazyInitSyncPlugin(room.currentExpireId)
]
};
- // when cross origin, add token to headers and query
- if (option.url.startsWith(window.location.origin) || option.url.startsWith("/api/movie")) {
- // option.headers = {
- // ...option.headers,
- // Authorization: roomToken.value
- // };
- option.url = option.url.includes("?")
- ? `${option.url}&token=${roomToken.value}`
- : `${option.url}?token=${roomToken.value}`;
- }
+
if (room.currentMovie.base!.subtitles) {
+ let defaultUrl;
+ let useAssPlugin = false;
+
+ const defaultSubtitle = room.currentMovie.base!.subtitles;
+
+ for (let key in defaultSubtitle) {
+ if (defaultSubtitle[key].hasOwnProperty("url")) {
+ if (defaultSubtitle[key].type === "ass") {
+ if (defaultSubtitle[key].url.startsWith("/api/movie/proxy/")) {
+ defaultUrl = window.location.origin + room.currentMovie.base!.subtitles[key].url;
+ useAssPlugin = true;
+ break;
+ }
+
+ if (defaultSubtitle[key].url.startsWith("http")) {
+ defaultUrl = room.currentMovie.base!.subtitles[key].url;
+ useAssPlugin = true;
+ break;
+ }
+
+ ElNotification.error({
+ title: "错误",
+ message: "字幕文件地址错误!ASS字幕解析将失效!"
+ });
+ useAssPlugin = false;
+ break;
+ } else {
+ useAssPlugin = false;
+ break;
+ }
+ } else break;
+ }
+
option.plugins!.push(newLazyInitSubtitlePlugin(room.currentMovie.base!.subtitles));
+ // return;
+ useAssPlugin &&
+ option.plugins!.push(
+ artplayerPluginAss({
+ // debug: true,
+ subUrl: defaultUrl
+ })
+ );
}
return option;
diff --git a/tsconfig.json b/tsconfig.json
index 7710be2..7833fa4 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,6 +11,7 @@
"compilerOptions": {
"target": "ES2016",
"noImplicitAny": true,
- "moduleResolution": "bundler"
+ "moduleResolution": "bundler",
+ "allowJs": true,
}
}