Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/common/apis/twitchApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const injectStore = (_store: AppMiddlewareAPI) => {
};

const TWITCH_CLIENT_ID = process.env.REACT_APP_TWITCH_CLIENT_ID ?? '';
const TWITCH_CLIPS_CDN = 'https://clips-media-assets2.twitch.tv';

const twitchApiClient = axios.create({
baseURL: 'https://api.twitch.tv/helix/',
Expand All @@ -26,21 +27,30 @@ const twitchGqlClient = axios.create({
const getDirectUrl = async (id: string): Promise<string | undefined> => {
const data = [
{
operationName: 'ClipsDownloadButton',
operationName: 'VideoAccessToken_Clip',
variables: {
slug: id,
},
extensions: {
persistedQuery: {
version: 1,
sha256Hash: '6e465bb8446e2391644cf079851c0cb1b96928435a240f07ed4b240f0acc6f1b',
sha256Hash: '36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11',
},
},
},
];

const resp = await twitchGqlClient.post('', data);
const [respData] = resp.data;

if (!respData.data.clip) {
throw new Error('Clip not found');
}

if (!respData.data.clip.videoQualities || respData.data.clip.videoQualities.length === 0) {
throw new Error('No video qualities available');
}

const playbackAccessToken = respData.data.clip.playbackAccessToken;
const url =
respData.data.clip.videoQualities[0].sourceURL +
Expand Down Expand Up @@ -79,11 +89,16 @@ const getGame = async (id: string): Promise<TwitchGame> => {
return data.data[0];
};

const getFallbackM3u8Url = (id: string): string => {
return `${TWITCH_CLIPS_CDN}/${id}/AT-cm%7C${id}.m3u8`;
};

const twitchApi = {
getClip,
getVideo,
getGame,
getDirectUrl,
getFallbackM3u8Url,
};

export default twitchApi;
5 changes: 5 additions & 0 deletions src/features/clips/providers/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface ClipProvider {
getUrl(id: string): string | undefined;
getEmbedUrl(id: string): string | undefined;
getAutoplayUrl(id: string): Promise<string | undefined>;
getFallbackM3u8Url?(id: string): string | undefined;
}

class CombinedClipProvider implements ClipProvider {
Expand Down Expand Up @@ -65,6 +66,10 @@ class CombinedClipProvider implements ClipProvider {
const [provider, idPart] = this.getProviderAndId(id);
return await provider?.getAutoplayUrl(idPart);
}
getFallbackM3u8Url(id: string): string | undefined {
const [provider, idPart] = this.getProviderAndId(id);
return provider?.getFallbackM3u8Url?.(idPart);
}

setProviders(providers: string[]) {
logger.info('setProviders', providers);
Expand Down
4 changes: 4 additions & 0 deletions src/features/clips/providers/twitchClip/twitchClipProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ class TwitchClipProvider implements ClipProvider {
return `https://clips.twitch.tv/${id}`;
}

getFallbackM3u8Url(id: string): string | undefined {
return twitchApi.getFallbackM3u8Url(id);
}

getEmbedUrl(id: string): string | undefined {
return `https://clips.twitch.tv/embed?clip=${id}&autoplay=true&parent=${window.location.hostname}`;
}
Expand Down
7 changes: 5 additions & 2 deletions src/features/clips/queue/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,11 @@ function Player({ className }: PlayerProps) {
}
} catch (err) {
if (Flag) {
setError('Failed to load video');
setVideoSrc(undefined);
const fallbackUrl = clipProvider.getFallbackM3u8Url(currentClip.id);
setVideoSrc(fallbackUrl);
setError(null);
} else {
setError('Failed to load video. Please make an Issue Request on GitHub. Thank you!');
}
}
};
Expand Down
Loading