switch to query param based Auth (#103)
* switch to query param based Auth * remove some changes commited by mistake. * fixed tslint issues * change the dash to authorization header * remove credentials headers
This commit is contained in:
Родитель
88acf93e2d
Коммит
ac8278a006
|
@ -1,4 +1,6 @@
|
|||
import { WidgetGeneralError } from '../../../widgets/src';
|
||||
import { MediaApi } from '../media/media-api.class';
|
||||
import { VideoContentToken } from '../media/media.definitions';
|
||||
import { TokenHandler } from './token-handler.class';
|
||||
|
||||
export class AvaAPi {
|
||||
|
@ -29,14 +31,14 @@ export class AvaAPi {
|
|||
headers['Authorization'] = `Bearer ${TokenHandler.avaAPIToken}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
credentials: 'include',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${TokenHandler.avaAPIToken}`
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
const data: VideoContentToken = await response.json();
|
||||
MediaApi.contentToken = data.Token;
|
||||
if (!data.ExpirationDate) {
|
||||
throw new WidgetGeneralError('Invalid cookie expiration');
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ export class MediaApi {
|
|||
private static _rtspStream: string | undefined;
|
||||
private static _format = MediaApi.supportsMediaSource() ? VideoFormat.DASH : VideoFormat.HLS;
|
||||
private static _videoEntity: VideoEntity;
|
||||
private static _contentToken: string;
|
||||
|
||||
public static supportsMediaSource(): boolean {
|
||||
return !!window.MediaSource;
|
||||
|
@ -16,6 +17,14 @@ export class MediaApi {
|
|||
this._rtspStream = this._videoEntity?.properties?.contentUrls?.rtspTunnelUrl;
|
||||
}
|
||||
|
||||
public static set contentToken(value: string) {
|
||||
this._contentToken = value;
|
||||
}
|
||||
|
||||
public static get contentToken() {
|
||||
return this._contentToken;
|
||||
}
|
||||
|
||||
public static isApple(): boolean {
|
||||
return !!navigator.vendor && navigator.vendor.includes('Apple');
|
||||
}
|
||||
|
@ -37,18 +46,21 @@ export class MediaApi {
|
|||
}
|
||||
|
||||
public static get rtspStream() {
|
||||
return this._rtspStream;
|
||||
return this.addTokenQueryParam(this._rtspStream, 'authorization');
|
||||
}
|
||||
|
||||
public static get liveStream(): string {
|
||||
// if RTSP is present use RTSP URL.
|
||||
let liveUrl = '';
|
||||
if (this._rtspStream && this.supportsMediaSource()) {
|
||||
return this._rtspStream;
|
||||
}
|
||||
const format = MediaApi._format === VideoFormat.HLS ? 'm3u8-cmaf' : 'mpd-time-cmaf';
|
||||
const extension = MediaApi._format === VideoFormat.HLS ? '.m3u8' : '.mpd';
|
||||
liveUrl = this.rtspStream;
|
||||
} else {
|
||||
const format = MediaApi._format === VideoFormat.HLS ? 'm3u8-cmaf' : 'mpd-time-cmaf';
|
||||
const extension = MediaApi._format === VideoFormat.HLS ? '.m3u8' : '.mpd';
|
||||
|
||||
return `${this.baseStream}/manifest(format=${format})${extension}`;
|
||||
liveUrl = `${this.baseStream}/manifest(format=${format})${extension}`;
|
||||
}
|
||||
return MediaApi._format === VideoFormat.HLS ? this.addTokenQueryParam(liveUrl) : liveUrl;
|
||||
}
|
||||
|
||||
public static getVODStream(range: IExpandedTimeRange = null): string {
|
||||
|
@ -66,7 +78,8 @@ export class MediaApi {
|
|||
}
|
||||
}
|
||||
|
||||
return `${this.baseStream}/manifest(format=${format}${range_query})${extension}`;
|
||||
const url = `${this.baseStream}/manifest(format=${format}${range_query})${extension}`;
|
||||
return MediaApi._format === VideoFormat.HLS ? this.addTokenQueryParam(url) : url;
|
||||
}
|
||||
|
||||
public static getVODStreamForCLip(startTime: Date, endTime: Date): string {
|
||||
|
@ -80,15 +93,11 @@ export class MediaApi {
|
|||
range_query = `,starttime=${startTimeISOFormat},endtime=${endTimeISOFormat}`;
|
||||
}
|
||||
|
||||
return `${this.baseStream}/manifest(format=${format}${range_query})${extension}`;
|
||||
const url = `${this.baseStream}/manifest(format=${format}${range_query})${extension}`;
|
||||
return MediaApi._format === VideoFormat.HLS ? this.addTokenQueryParam(url) : url;
|
||||
}
|
||||
|
||||
public static getAvailableMedia(
|
||||
precision: Precision,
|
||||
range: IExpandedTimeRange = null,
|
||||
allowCrossSiteCredentials = true,
|
||||
token?: string
|
||||
): Promise<Response> {
|
||||
public static getAvailableMedia(precision: Precision, range: IExpandedTimeRange = null): Promise<Response> {
|
||||
// time ranges are required for month, day and full
|
||||
if ((precision === Precision.MONTH || precision === Precision.DAY || precision === Precision.FULL) && !range) {
|
||||
throw Error('wrong parameters');
|
||||
|
@ -100,17 +109,7 @@ export class MediaApi {
|
|||
const url = `${this.baseStream}/availableMedia${range_query}`;
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const requestInit: RequestInit = {};
|
||||
|
||||
if (allowCrossSiteCredentials) {
|
||||
requestInit.credentials = 'include';
|
||||
}
|
||||
|
||||
if (token) {
|
||||
requestInit.headers = {
|
||||
Authorization: `Bearer ${token}`
|
||||
};
|
||||
}
|
||||
const requestInit: RequestInit = { headers: { Authorization: `Bearer ${this.contentToken}` } };
|
||||
return fetch(url, requestInit);
|
||||
}
|
||||
|
||||
|
@ -127,4 +126,10 @@ export class MediaApi {
|
|||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private static addTokenQueryParam(urlString: string, paramName = 'token') {
|
||||
const url = new URL(urlString);
|
||||
url.searchParams.set(paramName, encodeURIComponent(this.contentToken));
|
||||
return url.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,3 +84,8 @@ export interface SystemData {
|
|||
lastModifiedByType?: string;
|
||||
lastModifiedAt?: Date;
|
||||
}
|
||||
|
||||
export interface VideoContentToken {
|
||||
readonly ExpirationDate?: Date;
|
||||
readonly Token?: string;
|
||||
}
|
||||
|
|
|
@ -83,13 +83,7 @@ export class PlayerComponent extends FASTElement {
|
|||
PlayerWrapper.setDebugMode(debug);
|
||||
}
|
||||
|
||||
public async init(
|
||||
allowCrossSiteCredentials = true,
|
||||
accessToken?: string,
|
||||
allowedControllers?: ControlPanelElements[],
|
||||
clipTimeRange?: IClipTimeRange,
|
||||
isMuted?: boolean
|
||||
) {
|
||||
public async init(allowedControllers?: ControlPanelElements[], clipTimeRange?: IClipTimeRange, isMuted?: boolean) {
|
||||
if (!this.connected) {
|
||||
return;
|
||||
}
|
||||
|
@ -140,10 +134,10 @@ export class PlayerComponent extends FASTElement {
|
|||
|
||||
this.isMuted = isMuted ?? true;
|
||||
|
||||
this.initializePlayer(this.allowedControllers, allowCrossSiteCredentials, accessToken);
|
||||
this.initializePlayer(this.allowedControllers);
|
||||
}
|
||||
|
||||
public async initializePlayer(allowedControllers: ControlPanelElements[], allowCrossSiteCredentials = true, accessToken?: string) {
|
||||
public async initializePlayer(allowedControllers: ControlPanelElements[]) {
|
||||
if (this.player) {
|
||||
// If there was an existing error -clear state
|
||||
this.clearError();
|
||||
|
@ -166,11 +160,6 @@ export class PlayerComponent extends FASTElement {
|
|||
|
||||
this.player.addLoading();
|
||||
|
||||
if (accessToken) {
|
||||
this.player.accessToken = accessToken;
|
||||
}
|
||||
this.player.allowCrossCred = allowCrossSiteCredentials;
|
||||
|
||||
if (!MediaApi.videoFlags.canStream || (!MediaApi.baseStream && (!MediaApi.videoFlags.isInUse || !MediaApi.liveStream))) {
|
||||
this.hasError = true;
|
||||
this.player.removeLoading();
|
||||
|
@ -199,12 +188,6 @@ export class PlayerComponent extends FASTElement {
|
|||
this.classList.remove('loading');
|
||||
}
|
||||
|
||||
public setPlaybackAuthorization(accessToken: string) {
|
||||
if (accessToken) {
|
||||
this.player.accessToken = accessToken;
|
||||
}
|
||||
}
|
||||
|
||||
public async initializeAvailableMedia(checkForLive: boolean) {
|
||||
await this.fetchAvailableYears();
|
||||
|
||||
|
@ -399,7 +382,7 @@ export class PlayerComponent extends FASTElement {
|
|||
|
||||
public switchToDash() {
|
||||
MediaApi.rtspStream = '';
|
||||
this.initializePlayer(this.allowedControllers, this.player.allowCrossCred, this.player.accessToken);
|
||||
this.initializePlayer(this.allowedControllers);
|
||||
}
|
||||
|
||||
public retryStreaming() {
|
||||
|
@ -464,23 +447,18 @@ export class PlayerComponent extends FASTElement {
|
|||
|
||||
private async fetchAvailableSegments(startDate: IExpandedDate, end: IExpandedDate): Promise<IAvailableMediaResponse> {
|
||||
try {
|
||||
const availableHours = await MediaApi.getAvailableMedia(
|
||||
Precision.FULL,
|
||||
{
|
||||
start: {
|
||||
year: startDate.year,
|
||||
month: startDate.month,
|
||||
day: startDate.day
|
||||
},
|
||||
end: {
|
||||
year: end.year,
|
||||
month: end.month,
|
||||
day: end.day
|
||||
}
|
||||
const availableHours = await MediaApi.getAvailableMedia(Precision.FULL, {
|
||||
start: {
|
||||
year: startDate.year,
|
||||
month: startDate.month,
|
||||
day: startDate.day
|
||||
},
|
||||
this.player.allowCrossCred,
|
||||
this.player.accessToken
|
||||
);
|
||||
end: {
|
||||
year: end.year,
|
||||
month: end.month,
|
||||
day: end.day
|
||||
}
|
||||
});
|
||||
|
||||
return await availableHours.json();
|
||||
} catch (error) {
|
||||
|
@ -695,7 +673,7 @@ export class PlayerComponent extends FASTElement {
|
|||
}
|
||||
|
||||
private async fetchAvailableYears() {
|
||||
const availableYears = await MediaApi.getAvailableMedia(Precision.YEAR, null, this.player.allowCrossCred, this.player.accessToken);
|
||||
const availableYears = await MediaApi.getAvailableMedia(Precision.YEAR, null);
|
||||
try {
|
||||
const yearRanges: IAvailableMediaResponse = await availableYears.json();
|
||||
|
||||
|
@ -726,23 +704,18 @@ export class PlayerComponent extends FASTElement {
|
|||
private async fetchAvailableMonths(year: number) {
|
||||
// Take available months according to year
|
||||
try {
|
||||
const availableMonths = await MediaApi.getAvailableMedia(
|
||||
Precision.MONTH,
|
||||
{
|
||||
start: {
|
||||
year: year,
|
||||
month: 1,
|
||||
day: 1
|
||||
},
|
||||
end: {
|
||||
year: year,
|
||||
month: 12,
|
||||
day: 1
|
||||
}
|
||||
const availableMonths = await MediaApi.getAvailableMedia(Precision.MONTH, {
|
||||
start: {
|
||||
year: year,
|
||||
month: 1,
|
||||
day: 1
|
||||
},
|
||||
this.player.allowCrossCred,
|
||||
this.player.accessToken
|
||||
);
|
||||
end: {
|
||||
year: year,
|
||||
month: 12,
|
||||
day: 1
|
||||
}
|
||||
});
|
||||
const monthRanges: IAvailableMediaResponse = await availableMonths.json();
|
||||
|
||||
// Get last available month
|
||||
|
@ -770,23 +743,18 @@ export class PlayerComponent extends FASTElement {
|
|||
const lastDayOfMonth = new Date(year, month, 1, 0, 0, 0);
|
||||
lastDayOfMonth.setDate(lastDayOfMonth.getDate() - 1);
|
||||
// fetch available days
|
||||
const availableDays = await MediaApi.getAvailableMedia(
|
||||
Precision.DAY,
|
||||
{
|
||||
start: {
|
||||
year: year,
|
||||
month: month,
|
||||
day: 1
|
||||
},
|
||||
end: {
|
||||
year: year,
|
||||
month: month,
|
||||
day: lastDayOfMonth.getDate()
|
||||
}
|
||||
const availableDays = await MediaApi.getAvailableMedia(Precision.DAY, {
|
||||
start: {
|
||||
year: year,
|
||||
month: month,
|
||||
day: 1
|
||||
},
|
||||
this.player.allowCrossCred,
|
||||
this.player.accessToken
|
||||
);
|
||||
end: {
|
||||
year: year,
|
||||
month: month,
|
||||
day: lastDayOfMonth.getDate()
|
||||
}
|
||||
});
|
||||
|
||||
const dayRanges: IAvailableMediaResponse = await availableDays.json();
|
||||
|
||||
|
|
|
@ -33,11 +33,9 @@ export class PlayerWrapper {
|
|||
private isClip = false;
|
||||
private isLoaded = false;
|
||||
private duringSegmentJump = false;
|
||||
private _accessToken = '';
|
||||
private _mimeType: MimeType;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private controls: any;
|
||||
private _allowCrossCred = true;
|
||||
private timestampOffset: number;
|
||||
private firstSegmentStartSeconds: number;
|
||||
private date: Date;
|
||||
|
@ -110,9 +108,9 @@ export class PlayerWrapper {
|
|||
if (this._liveStream === MediaApi.rtspStream) {
|
||||
// getVideo API may one day return the RTSP URL, until then, hard-code it.
|
||||
url.searchParams.set('rtsp', encodeURIComponent('rtsp://localhost'));
|
||||
if (this.accessToken) {
|
||||
url.searchParams.set('authorization', this.accessToken);
|
||||
}
|
||||
// if (MediaApi.contentToken) {
|
||||
// url.searchParams.set('authorization', MediaApi.contentToken);
|
||||
// }
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
@ -123,14 +121,6 @@ export class PlayerWrapper {
|
|||
this._vodStream = value;
|
||||
}
|
||||
|
||||
public set allowCrossCred(value: boolean) {
|
||||
this._allowCrossCred = value;
|
||||
}
|
||||
|
||||
public get allowCrossCred() {
|
||||
return this._allowCrossCred;
|
||||
}
|
||||
|
||||
public set mimeType(value: MimeType) {
|
||||
this._mimeType = value;
|
||||
}
|
||||
|
@ -154,14 +144,6 @@ export class PlayerWrapper {
|
|||
this.video.play();
|
||||
}
|
||||
|
||||
public set accessToken(accessToken: string) {
|
||||
this._accessToken = accessToken;
|
||||
}
|
||||
|
||||
public get accessToken() {
|
||||
return this._accessToken;
|
||||
}
|
||||
|
||||
public set currentDate(startDate: Date) {
|
||||
this._currentDate = startDate;
|
||||
}
|
||||
|
@ -362,10 +344,7 @@ export class PlayerWrapper {
|
|||
this._availableSegments?.timeRanges[this._availableSegments?.timeRanges.length - 1]?.start,
|
||||
this._currentDate
|
||||
);
|
||||
const firstSegmentStartSeconds = extractRealTimeFromISO(
|
||||
this._availableSegments?.timeRanges[0]?.start,
|
||||
this._currentDate
|
||||
);
|
||||
const firstSegmentStartSeconds = extractRealTimeFromISO(this._availableSegments?.timeRanges[0]?.start, this._currentDate);
|
||||
this.disableNextSegmentButton(this.currentSegment.startSeconds === lastSegmentStartSeconds);
|
||||
this.disablePrevSegmentButton(this.currentSegment.startSeconds === firstSegmentStartSeconds);
|
||||
}
|
||||
|
@ -685,13 +664,16 @@ export class PlayerWrapper {
|
|||
}
|
||||
|
||||
private authenticationHandler(type: shaka_player.net.NetworkingEngine.RequestType, request: shaka_player.extern.Request) {
|
||||
request['allowCrossSiteCredentials'] = this._allowCrossCred;
|
||||
if (!this._accessToken) {
|
||||
if (!MediaApi.contentToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add authorization header
|
||||
request.headers['Authorization'] = `Bearer ${this._accessToken}`;
|
||||
const url: string = request['uris'][0];
|
||||
if (url.indexOf('http') != -1) {
|
||||
const urlRequest = new URL(url);
|
||||
urlRequest.searchParams.set('token', MediaApi.contentToken);
|
||||
request['uris'][0] = urlRequest.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private onShakaMetadata(event: shaka_player.PlayerEvents.EmsgEvent) {
|
||||
|
@ -733,6 +715,8 @@ export class PlayerWrapper {
|
|||
private async onTrackChange() {
|
||||
// Get player manifest
|
||||
if (!MediaApi.supportsMediaSource()) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const date = (this.video as any).getStartDate();
|
||||
Logger.log(`video start date is ${date} ${date.getUTCDate()}`);
|
||||
|
|
|
@ -88,19 +88,18 @@ export class Player extends BaseWidget {
|
|||
public setSource(source: ISource) {
|
||||
this.source = source;
|
||||
MediaApi.videoEntity = this.source.videoEntity;
|
||||
MediaApi.contentToken = this.source.authenticationToken;
|
||||
|
||||
this.setLocalization(this.config?.locale, ['common', 'player']);
|
||||
if (this.loaded) {
|
||||
const playerComponent: PlayerComponent = this.shadowRoot.querySelector('media-player');
|
||||
playerComponent.cameraName = AvaAPi.videoName;
|
||||
playerComponent.init(this.source.allowCrossSiteCredentials, this.source.authenticationToken, this.allowedControllers);
|
||||
playerComponent.init(this.allowedControllers);
|
||||
}
|
||||
}
|
||||
|
||||
public setPlaybackAuthorization(token: string) {
|
||||
const playerComponent: PlayerComponent = this.shadowRoot.querySelector('media-player');
|
||||
|
||||
playerComponent.setPlaybackAuthorization(token);
|
||||
MediaApi.contentToken = token;
|
||||
}
|
||||
|
||||
public set apiBase(apiBase: string) {
|
||||
|
@ -120,7 +119,7 @@ export class Player extends BaseWidget {
|
|||
// If set source state
|
||||
if (this.source) {
|
||||
playerComponent.cameraName = AvaAPi.videoName;
|
||||
playerComponent.init(this.source.allowCrossSiteCredentials, this.source.authenticationToken, this.allowedControllers);
|
||||
playerComponent.init(this.allowedControllers);
|
||||
return;
|
||||
}
|
||||
// Configuration state - work with AVA API
|
||||
|
@ -136,7 +135,7 @@ export class Player extends BaseWidget {
|
|||
// Authorize video
|
||||
await AvaAPi.authorize();
|
||||
playerComponent.cameraName = AvaAPi.videoName;
|
||||
playerComponent.init(true, '', this.allowedControllers, this.clipTimeRange, this.isMuted);
|
||||
playerComponent.init(this.allowedControllers, this.clipTimeRange, this.isMuted);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
|
@ -172,7 +171,7 @@ export class Player extends BaseWidget {
|
|||
private handelFallback(error: HttpError) {
|
||||
const player: PlayerComponent = this.shadowRoot.querySelector('media-player');
|
||||
player.cameraName = AvaAPi.videoName;
|
||||
player.init(true, '', this.allowedControllers);
|
||||
player.init(this.allowedControllers);
|
||||
player.handleError(error);
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче