@api.video/private-video-session
Version:
api.video private video session
283 lines (248 loc) • 7.72 kB
text/typescript
import md5 from "md5";
const isAssetUrlOptions = (options: Options): options is AssetUrlOptions => {
return options && "assetUrl" in options;
};
type AssetUrlOptions = {
assetUrl: string;
};
type TokenOptions = {
token: string;
videoId: string;
};
type Domain = {
live?: string;
vod?: string;
embed?: string;
collector?: string;
};
type Options = (AssetUrlOptions | TokenOptions) & { domains?: Domain };
type VideoIdPrivateToken = {
host?: string;
videoId: string;
privateToken: string;
environment?: Environment;
videoType?: VideoType;
subdomain?: string;
};
type Environment = "production" | "staging";
type VideoType = "vod" | "live";
type UrlType = {
re: RegExp;
type: VideoType;
env: Environment;
};
export class PrivateVideoSession {
private videoId: string;
private privateToken: string;
private environment: Environment;
private videoType: VideoType;
private sessionToken?: string;
private domains: Domain;
constructor(options: Options) {
this.domains = options.domains || {};
const videoIdPrivateToken: VideoIdPrivateToken = isAssetUrlOptions(options)
? this.initFromAssetUrl(options.assetUrl)
: this.initFromToken(options.token, options.videoId);
this.videoId = videoIdPrivateToken.videoId;
this.privateToken = videoIdPrivateToken.privateToken;
this.environment = videoIdPrivateToken.environment || "production";
this.videoType = videoIdPrivateToken.videoType || "vod";
if (Object.keys(this.domains).length > 0 && !this.domains[this.videoType]) {
throw new Error(
`The ${this.videoType} domain is required when using ${this.videoType} assets with custom domains.`
);
}
}
public async getHlsUrl(): Promise<string> {
return this.videoType === "vod"
? `https://${this.getMediaHost()}/vod/${this.videoId}/token/${
this.privateToken
}/hls/manifest.m3u8?avh=${await this.getSessionToken()}`
: `https://${this.getMediaHost()}/private/${this.privateToken}/${
this.videoId
}.m3u8?avh=${await this.getSessionToken()}`;
}
public async getPlayerUrl(): Promise<string> {
return `https://${this.getEmbedHost()}/${this.videoType}/${
this.videoId
}?token=${this.privateToken}&avh=${await this.getSessionToken()}`;
}
public async getMp4Url(): Promise<string> {
if (this.videoType === "live") {
throw new Error("The mp4 url is not available for live streams.");
}
return `https://${this.getMediaHost()}/vod/${this.videoId}/token/${
this.privateToken
}/mp4/source.mp4?avh=${await this.getSessionToken()}`;
}
public async getThumbnailUrl(): Promise<string> {
return this.videoType === "vod"
? `https://${this.getMediaHost()}/vod/${this.videoId}/token/${
this.privateToken
}/thumbnail.jpg?avh=${await this.getSessionToken()}`
: `https://${this.getMediaHost()}/private/${this.privateToken}/${
this.videoId
}/thumbnail.jpg?avh=${await this.getSessionToken()}`;
}
public async getSessionToken(): Promise<string> {
if (this.sessionToken) {
return this.sessionToken;
}
const sessionStorageKey = md5(`xTokenSession_${this.privateToken}`);
const storageSessionToken = sessionStorage.getItem(sessionStorageKey);
if (storageSessionToken) {
this.sessionToken = storageSessionToken;
return storageSessionToken;
}
const newSessionToken = await this.fetchSessionToken();
if (newSessionToken) {
sessionStorage.setItem(sessionStorageKey, newSessionToken);
this.sessionToken = newSessionToken;
}
return newSessionToken;
}
private getEmbedHost(): string {
if (this.domains.embed) {
return this.domains.embed;
}
return this.environment === "production"
? "embed.api.video"
: "embed.staging.api.video";
}
private getMediaHost(): string {
if (this.domains[this.videoType]) {
return this.domains[this.videoType]!;
}
return this.environment === "production"
? `${this.videoType}.api.video`
: `${this.videoType}.staging.api.video`;
}
private initFromAssetUrl(privateAssetUrl: string): VideoIdPrivateToken {
const urls: UrlType[] = [
{
re: new RegExp(
`https:\/\/${
this.domains.vod || "vod.api.video"
}\/vod\/(?<videoId>[^\/]*)\/token\/(?<privateToken>[^\/]*)\/.*`,
"gm"
),
type: "vod",
env: "production",
},
{
re: new RegExp(
`https:\/\/${
this.domains.vod || "vod.staging.api.video"
}\/vod\/(?<videoId>[^\/]*)\/token\/(?<privateToken>[^\/]*)\/.*`,
"gm"
),
type: "vod",
env: "staging",
},
{
re: new RegExp(
`https:\/\/${
this.domains.embed || "embed.api.video"
}\/vod\/(?<videoId>[^\/]*)\\?token=(?<privateToken>[^\/]*)`,
"gm"
),
type: "vod",
env: "production",
},
{
re: new RegExp(
`https:\/\/${
this.domains.embed || "embed.staging.api.video"
}\/vod\/(?<videoId>[^\/]*)\\?token=(?<privateToken>[^\/]*)`,
"gm"
),
type: "vod",
env: "staging",
},
{
re: new RegExp(
`https:\/\/${
this.domains.live || "live.api.video"
}\/private\/(?<privateToken>[^\/]*)\/(?<videoId>[^\.]*)\..*`,
"gm"
),
type: "live",
env: "production",
},
{
re: new RegExp(
`https:\/\/${
this.domains.live || "live.staging.api.video"
}\/private\/(?<privateToken>[^\/]*)\/(?<videoId>[^\.]*)\..*`,
"gm"
),
type: "live",
env: "staging",
},
{
re: new RegExp(
`https:\/\/${
this.domains.embed || "embed.api.video"
}\/live\/(?<videoId>[^\/]*)\\?token=(?<privateToken>[^\/]*)`,
"gm"
),
type: "live",
env: "production",
},
{
re: new RegExp(
`https:\/\/${
this.domains.embed || "embed.staging.api.video"
}\/live\/(?<videoId>[^\/]*)\\?token=(?<privateToken>[^\/]*)`,
"gm"
),
type: "live",
env: "staging",
},
];
for (const url of urls) {
const parsed = url.re.exec(privateAssetUrl);
if (parsed?.groups?.videoId && parsed.groups.privateToken) {
return {
videoId: parsed.groups.videoId,
privateToken: parsed.groups.privateToken,
environment: url.env,
videoType: url.type,
};
}
}
throw new Error(
"The provided URL doesn't look like an api.video private asset URL."
);
}
private initFromToken(
privateToken: string,
videoId: string
): VideoIdPrivateToken {
if (!privateToken || !videoId) {
throw new Error("The provided token and videoId are invalid.");
}
return {
videoId,
privateToken,
};
}
private async fetchSessionToken(): Promise<string> {
const response = await fetch(
this.videoType === "vod"
? `https://${this.getMediaHost()}/vod/${this.videoId}/token/${
this.privateToken
}/session`
: `https://${this.getMediaHost()}/private/${this.privateToken}/${
this.videoId
}/session`
);
if (response.status !== 200) {
throw new Error(
"Can't retrieve a session token. The private token has probably already been used."
);
}
const json = await response.json();
return json.session_token;
}
}