Skip to content

Commit 90a7af5

Browse files
authored
Fix Vimeo downloads by changing from dash to hls (#489)
1 parent 275636e commit 90a7af5

3 files changed

Lines changed: 106 additions & 211 deletions

File tree

chrome/background/background.mjs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,19 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
235235
});
236236
return true;
237237
} else if (msg.type === MessageTypes.FRAME_ADDED) {
238+
// Preserve sources key to this frame's new URL
239+
// This is needed because resetSelfAndChildren clears all sources, but we might have
240+
// already detected a source for this frame via onHeadersReceived (e.g. Vimeo)
241+
const preservedSources = frame.getSources().filter((s) => s.url === msg.url);
242+
238243
const playerCount = frame.resetSelfAndChildren();
239244
frame.url = msg.url;
245+
246+
// Restore preserved sources
247+
preservedSources.forEach((s) => {
248+
frame.getSources().push(s);
249+
});
250+
240251
tab.playerCount -= playerCount;
241252
tab.playerCount = Math.max(0, tab.playerCount);
242253
checkURLMatch(frame);
@@ -1302,18 +1313,12 @@ chrome.webRequest.onHeadersReceived.addListener(
13021313
if ((details.statusCode >= 400 && details.statusCode < 600) || details.statusCode === 204) {
13031314
return; // Client or server error. Ignore it
13041315
}
1305-
1306-
if (url.startsWith('https://vod-adaptive') && url.includes('playlist.json') && url.includes('vimeo')) {
1316+
if (url.startsWith('https://player.vimeo.com') && (url.includes('config?') || url.includes('video'))) {
13071317
ext = 'vmpatch';
13081318
} else if (details.initiator &&
13091319
initiatorBlacklist.some((a) => {
13101320
return details.initiator.startsWith(a);
13111321
})) {
1312-
if (ext === 'json') {
1313-
1314-
} else {
1315-
return;
1316-
}
13171322
}
13181323

13191324
const output = CustomSourcePatternsMatcher.match(url);
@@ -1323,13 +1328,6 @@ chrome.webRequest.onHeadersReceived.addListener(
13231328

13241329
if (BackgroundUtils.isSubtitles(ext)) {
13251330
return handleSubtitles(url, frame, frame.requestHeaders.get(details.requestId));
1326-
} else if (ext === 'json') {
1327-
// Vimeo. Check if filename is master.json
1328-
const filename = URLUtils.get_file_name(url);
1329-
if (filename === 'master.json' && url.includes('/video/')) {
1330-
ext = 'mpd';
1331-
details.url = URLUtils.strip_queryhash(url).replace('master.json', 'master.mpd');
1332-
}
13331331
}
13341332

13351333
let mode = URLUtils.getModeFromExtension(ext);
Lines changed: 94 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
1-
import {DefaultPlayerEvents} from '../../enums/DefaultPlayerEvents.mjs';
2-
import {PlayerModes} from '../../enums/PlayerModes.mjs';
3-
import {RequestUtils} from '../../utils/RequestUtils.mjs';
4-
import DashPlayer from '../dash/DashPlayer.mjs';
5-
import {Vimeo2Dash} from './Vimeo2Dash.mjs';
1+
import { DefaultPlayerEvents } from '../../enums/DefaultPlayerEvents.mjs';
2+
import { PlayerModes } from '../../enums/PlayerModes.mjs';
3+
import { RequestUtils } from '../../utils/RequestUtils.mjs';
4+
import HLSPlayer from "../hls/HLSPlayer.mjs";
65

7-
export default class VMPlayer extends DashPlayer {
6+
export default class VMPlayer extends HLSPlayer {
87
constructor(client, options) {
98
super(client, options);
109
}
1110

1211
async setSource(source) {
1312
try {
14-
let manifest;
15-
try {
16-
const hc = [];
13+
const isEmbed = !source.url.includes('config?');
14+
const hc = [];
15+
if (Array.isArray(source.headers)) {
16+
source.headers.forEach((h) => {
17+
hc.push({
18+
operation: 'set',
19+
header: h.name,
20+
value: h.value,
21+
});
22+
});
23+
} else {
1724
for (const key in source.headers) {
1825
if (Object.hasOwn(source.headers, key)) {
1926
hc.push({
@@ -23,27 +30,36 @@ export default class VMPlayer extends DashPlayer {
2330
});
2431
}
2532
}
33+
}
2634

27-
const xhr = await RequestUtils.request({
28-
url: source.url,
29-
header_commands: hc,
30-
responseType: 'json',
31-
});
35+
const xhr = await RequestUtils.request({
36+
url: source.url,
37+
header_commands: hc,
38+
responseType: isEmbed ? 'text' : 'json',
39+
});
3240

33-
const convert = new Vimeo2Dash();
34-
manifest = convert.playlistToDash(source.url, xhr.response);
35-
} catch (e) {
36-
throw e;
41+
const config = xhr.response;
42+
const hls = !isEmbed ? config?.request?.files?.hls : this.extractVimeoHlsUrlFromIframePlayer(config);
43+
if (!hls || !hls.cdns) {
44+
throw new Error('Vimeo HLS data not found');
3745
}
46+
const defaultCdn =
47+
hls.default_cdn && hls.cdns[hls.default_cdn]
48+
? hls.default_cdn
49+
: Object.keys(hls.cdns)[0];
50+
51+
let hlsUrl = hls.cdns[defaultCdn].url;
52+
53+
if (!hlsUrl) {
54+
throw new Error('Vimeo HLS URL missing');
55+
}
56+
57+
hlsUrl = hlsUrl.replace(/\\u0026/g, '&');
3858

39-
this.oldSource = source;
40-
const blob = new Blob([manifest], {
41-
type: 'application/dash+xml',
42-
});
43-
const uri = URL.createObjectURL(blob);
4459
this.source = source.copy();
45-
this.source.url = uri;
46-
this.source.mode = PlayerModes.ACCELERATED_DASH;
60+
this.source.url = hlsUrl;
61+
this.source.mode = PlayerModes.ACCELERATED_HLS;
62+
4763
} catch (e) {
4864
console.error(e);
4965
this.emit(DefaultPlayerEvents.ERROR, e);
@@ -63,4 +79,57 @@ export default class VMPlayer extends DashPlayer {
6379
getSource() {
6480
return this.source;
6581
}
82+
83+
extractVimeoHlsUrlFromIframePlayer(html) {
84+
85+
const config = this.extractJsonConfig(html, 'window.playerConfig =');
86+
87+
if (!config) {
88+
throw new Error('Vimeo iframe: playerConfig not found');
89+
}
90+
91+
return config?.request?.files?.hls;
92+
}
93+
94+
extractJsonConfig(html, prefix) {
95+
const startIndex = html.indexOf(prefix);
96+
if (startIndex === -1) return null;
97+
98+
// Move past the prefix
99+
let i = startIndex + prefix.length;
100+
101+
// Find the first '{'
102+
while (i < html.length && html[i] !== '{') {
103+
i++;
104+
}
105+
106+
if (i >= html.length) return null;
107+
108+
const jsonStart = i;
109+
let braceCount = 0;
110+
let foundStart = false;
111+
112+
// Iterate to find the matching closing brace
113+
for (; i < html.length; i++) {
114+
if (html[i] === '{') {
115+
braceCount++;
116+
foundStart = true;
117+
} else if (html[i] === '}') {
118+
braceCount--;
119+
}
120+
121+
if (foundStart && braceCount === 0) {
122+
// We found the end of the object
123+
const jsonString = html.substring(jsonStart, i + 1);
124+
try {
125+
return JSON.parse(jsonString);
126+
} catch (e) {
127+
return null;
128+
}
129+
}
130+
}
131+
132+
return null;
133+
}
134+
66135
}

chrome/player/players/vm/Vimeo2Dash.mjs

Lines changed: 0 additions & 172 deletions
This file was deleted.

0 commit comments

Comments
 (0)