Skip to content

Commit

Permalink
feat(player): ass subtitle support
Browse files Browse the repository at this point in the history
Co-authored-by: lainio24 <[email protected]>
  • Loading branch information
WakelessSloth56 and lainio24 committed Mar 12, 2024
1 parent c2dc461 commit 192c6e2
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 32 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
- [PCC-Studio](https://www.pccstudio.com):编写原始项目
- [AUIOC](https://www.auioc.org):技术支持
- [CommentCoreLibrary](https://github.com/jabbany/CommentCoreLibrary):弹幕支持
- [ASS.JS](https://github.com/weizhenye/ASS):字幕支持
1 change: 1 addition & 0 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ sed -i "s;{version};$branch@$commit;g" src/player.main.ts
# Build player.js
mkdir src/lib
curl -L https://github.com/jabbany/CommentCoreLibrary/raw/19db2962ed0ce637a2b99facdf8634d51bb1b503/dist/CommentCoreLibrary.js >src/lib/CommentCoreLibrary.js
curl -L https://github.com/weizhenye/ASS/blob/e6a3605a2343655d9ef80bdd7e9fe92f92edca22/dist/ass.js >src/lib/ass.js
tsc
rm public/player.js.map
# Minify player.js
Expand Down
35 changes: 26 additions & 9 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<tr>
<td><label for="video-file">Video:</label></td>
<td>
<input id="video-file" type="file" accept="video/mp4,.flv,.mkv" style="width:100%" />
<input id="video-file" type="file" accept="video/mp4,.flv,.mkv" style="width: 100%" />
</td>
</tr>
<tr>
Expand All @@ -35,7 +35,7 @@
<tr>
<td><label for="danmaku-file">Danmaku:</label></td>
<td>
<input id="danmaku-file" type="file" accept=".xml" style="width:100%" />
<input id="danmaku-file" type="file" accept=".xml" style="width: 100%" />
</td>
</tr>
<tr>
Expand All @@ -48,6 +48,12 @@
<td>Size Offset:</td>
<td><input id="danmaku-size-offset" type="number" value="-3" /></td>
</tr>
<tr>
<td><label for="subtitle-file">Subtitle:</label></td>
<td>
<input id="subtitle-file" type="file" accept=".ass" style="width: 100%" />
</td>
</tr>
<tr>
<td>Auto Play:</td>
<td><input id="auto-play" type="checkbox" /></td>
Expand Down Expand Up @@ -91,13 +97,24 @@
const danmaku = id('danmaku-file').files[0];
const danmakuUrl = danmaku ? URL.createObjectURL(danmaku) : null;

const player = new Player(id('player'), __player_metadata__, video.name, videoUrl, danmakuUrl, {
autoPlay: id('auto-play').checked,
fullscreen: id('fullscreen').checked,
muted: id('muted').checked,
danmakuTimeOffset: parseFloat(id('danmaku-time-offset').value),
danmakuSizeOffset: parseInt(id('danmaku-size-offset').value),
});
const subtitle = id('subtitle-file').files[0];
const subtitleUrl = subtitle ? URL.createObjectURL(subtitle) : null;

const player = new Player(
id('player'),
__player_metadata__,
video.name,
videoUrl,
danmakuUrl,
subtitleUrl,
{
autoPlay: id('auto-play').checked,
fullscreen: id('fullscreen').checked,
muted: id('muted').checked,
danmakuTimeOffset: parseFloat(id('danmaku-time-offset').value),
danmakuSizeOffset: parseInt(id('danmaku-size-offset').value),
}
);
window.player = player;
id('start-panel').remove();
id('player').removeAttribute('style');
Expand Down
2 changes: 1 addition & 1 deletion public/player.css
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ video {
}

.overlays {
position: absolute !important;
position: fixed !important;
bottom: 0;
top: 0;
left: 0;
Expand Down
4 changes: 4 additions & 0 deletions src/player.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class Player {
readonly container: HTMLDivElement;
readonly danmakuUrl: string;
commentManager;
readonly subtitleUrl: string;
subtitleManager: SubtitleManager;
private constructed: boolean;
elements: StrGenKV<HTMLElement> = {};
_dyn: StrAnyKV = {};
Expand All @@ -25,6 +27,7 @@ class Player {
title: string,
videoUrl: string,
danmakuUrl: string,
subtitleUrl: string,
options: PlayerOptions
) {
console.log('Version:', this.version);
Expand All @@ -48,6 +51,7 @@ class Player {
this.container = container;
this.danmakuUrl = danmakuUrl;
if (0) this.commentManager = new CommentManager(); // for type intellisense
this.subtitleUrl = subtitleUrl;
this.__bindElements();
this.__bindEvents();
{
Expand Down
94 changes: 72 additions & 22 deletions src/player.player.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const toggleByPlayerData = (dataName: string, thisClass: string) => {
const toggleDisplayByData = (dataName: string, thisClass: string) => {
let r = '';
for (let i = 0; i < 4; i++) {
r += `.player[data-${dataName}='${i < 2 ? 'true' : 'false'}'] `;
Expand All @@ -8,8 +8,24 @@ const toggleByPlayerData = (dataName: string, thisClass: string) => {
return r;
};

function toggleComponent(P: Player, key: string, on: () => void, onToast: string, off: () => void, offToast: string) {
if (!P._dyn[key]) {
P._dyn[key] = true;
on();
P.setContainerData(key, true);
P.toast(onToast);
} else {
P._dyn[key] = false;
off();
P.setContainerData(key, false);
P.toast(offToast);
}
}

const icon_danmaku_off =
'<path d="M14 1a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H4.414A2 2 0 0 0 3 11.586l-2 2V2a1 1 0 0 1 1-1zM2 0a2 2 0 0 0-2 2v12.793a.5.5 0 0 0 .854.353l2.853-2.853A1 1 0 0 1 4.414 12H14a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2z"/>';
const icon_subtitle_on =
'<path d="M3.708 7.755c0-1.111.488-1.753 1.319-1.753.681 0 1.138.47 1.186 1.107H7.36V7c-.052-1.186-1.024-2-2.342-2C3.414 5 2.5 6.05 2.5 7.751v.747c0 1.7.905 2.73 2.518 2.73 1.314 0 2.285-.792 2.342-1.939v-.114H6.213c-.048.615-.496 1.05-1.186 1.05-.84 0-1.319-.62-1.319-1.727zm6.14 0c0-1.111.488-1.753 1.318-1.753.682 0 1.139.47 1.187 1.107H13.5V7c-.053-1.186-1.024-2-2.342-2C9.554 5 8.64 6.05 8.64 7.751v.747c0 1.7.905 2.73 2.518 2.73 1.314 0 2.285-.792 2.342-1.939v-.114h-1.147c-.048.615-.497 1.05-1.187 1.05-.839 0-1.318-.62-1.318-1.727z"/><path d="M14 3a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zM2 2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z"/>';

const Icons = {
play: '',
Expand All @@ -22,6 +38,8 @@ const Icons = {
danmaku_on:
icon_danmaku_off +
'<path d="M3 3.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5M3 6a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9A.5.5 0 0 1 3 6m0 2.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5"/>',
subtitle_on: icon_subtitle_on,
subtitle_off: icon_subtitle_on.replace('d=', 'fill="#7e7e7e" d='),
} as const;

function icon<K extends keyof typeof Icons>(p: K) {
Expand Down Expand Up @@ -62,15 +80,17 @@ const __player_metadata__: PlayerMetadata = {
.children(
new EDC('button', 'playToggle') //
.class('play-toggle')
.css((s) => toggleByPlayerData('paused', s._attrs.class))
.title('Play/Pause')
.css((s) => toggleDisplayByData('paused', s._attrs.class))
.selfEvents({ click: (P) => P.togglePlay() })
.children(...newSpans('⏵', '⏸')),
new EDC('div') //
.class('volume-wrapper')
.children(
new EDC('button', 'muteToggle')
.class('mute-toggle')
.css((s) => toggleByPlayerData('muted', s._attrs.class))
.title('Mute/Unmute')
.css((s) => toggleDisplayByData('muted', s._attrs.class))
.selfEvents({
click: (P) => P.toggleMute(),
})
Expand Down Expand Up @@ -182,29 +202,44 @@ const __player_metadata__: PlayerMetadata = {
canplay: (_, E, V) => (E.textContent = fTime(V.duration)),
})
),
new EDC('div', 'danmaku-controls')
.condition((P) => (P.danmakuUrl ? true : false))
new EDC('button', 'subtitleToggle')
.condition(hasSubtitle)
.class('subtitle-toggle')
.title('Subtitle')
.css((s) => toggleDisplayByData('subtitle-on', s._attrs.class))
.selfEvents({
click: (P) =>
toggleComponent(
P,
'subtitleOn',
() => P.subtitleManager.show(),
'Subtitle On',
() => P.subtitleManager.hide(),
'Subtitle Off'
),
})
.children(...newSpans(icon('subtitle_on'), icon('subtitle_off'))),
new EDC('div')
.condition(hasDanmaku)
.class('danmaku-controls')
.children(
new EDC('button', 'danmakuToggle')
.condition((P) => (P.danmakuUrl ? true : false))
.class('danmaku-toggle')
.css((s) => toggleByPlayerData('danmaku-on', s._attrs.class))
.title('Danmaku')
.css((s) => toggleDisplayByData('danmaku-on', s._attrs.class))
.selfEvents({
click: (P) => {
if (!P._dyn.danmakuOn) {
P._dyn.danmakuOn = true;
P.commentManager.start();
P.setContainerData('danmakuOn', true);
P.toast('Danmaku On');
} else {
P._dyn.danmakuOn = false;
P.commentManager.clear();
P.commentManager.stop();
P.setContainerData('danmakuOn', false);
P.toast('Danmaku Off');
}
},
click: (P) =>
toggleComponent(
P,
'danmakuOn',
() => P.commentManager.start(),
'Danmaku On',
() => {
P.commentManager.clear();
P.commentManager.stop();
},
'Danmaku Off'
),
})
.children(...newSpans(icon('danmaku_on'), icon('danmaku_off'))),
new EDC('button', 'danmakuListToggle') //
Expand Down Expand Up @@ -257,7 +292,8 @@ const __player_metadata__: PlayerMetadata = {
),
new EDC('button', 'fullscreenToggle')
.class('fullscreen-toggle')
.css((s) => toggleByPlayerData('fullscreen', s._attrs.class))
.title('Fullscreen')
.css((s) => toggleDisplayByData('fullscreen', s._attrs.class))
.selfEvents({
click: (P) => P.toggleFullscreen(),
})
Expand Down Expand Up @@ -289,6 +325,20 @@ const __player_metadata__: PlayerMetadata = {
click: (P) => P.togglePlay(),
})
.children(
new EDC('div', 'subtitleStage')
.class('subtitle-stage container')
.condition(hasSubtitle)
.selfEvents({
create: (P, E) => {
P.subtitleManager = initSubtitle(E, P.video, P.subtitleUrl);
P.firePlayerEvent('subtitleload');
P._dyn.subtitleOn = true;
P.setContainerData('subtitleOn', true);
},
})
.videoEvents({
resize: (P) => P.subtitleManager.resize(),
}),
new EDC('div', 'danmakuStage')
.class('danmaku-stage container')
.condition(hasDanmaku)
Expand Down
31 changes: 31 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ type EventListenerMap<F extends AnyFunction> = {

type HTMLTagNames = keyof HTMLElementTagNameMap;

interface SubtitleManager {
new (
subtitle: string,
media: HTMLMediaElement,
options: {
container?: HTMLElement;
resampling?: 'video_width' | 'video_height' | 'script_width' | 'script_height';
}
): SubtitleManager;
resize(): void;
hide(): void;
show(): void;
destory(): void;
}

// ================================================================================================================== //

function clamp(number: number, min: number, max: number) {
Expand Down Expand Up @@ -146,6 +161,22 @@ function hasDanmaku(p: Player) {
return p.danmakuUrl ? true : false;
}

function initSubtitle(stage: HTMLElement, video: HTMLVideoElement, url: string) {
const req = new XMLHttpRequest();
req.open('GET', url, false);
req.send();
if (req.status === 200) {
// @ts-expect-error
const ass = new ASS(req.responseText, video, { container: stage, resampling: 'video_height' });
return ass as SubtitleManager;
}
return null;
}

function hasSubtitle(p: Player) {
return p.subtitleUrl ? true : false;
}

function danmakuCount(p: Player) {
return p.commentManager.timeline.length;
}

0 comments on commit 192c6e2

Please sign in to comment.