From 5d8d5f56f39b02e04da254753542acc086b5525a Mon Sep 17 00:00:00 2001 From: Pablo Martinez Garcia Date: Mon, 8 Feb 2021 10:49:09 +0100 Subject: [PATCH] [ATS-854] Add media tracks to player from webvtt rendition (#6626) * ATS-854 Add media tracks to player from webvtt rendition * ATS-854 Fix condition * ATS-854 Fix lint * ATS-854 Move logic to media player * ATS-854 Fix angular.json * ATS-854 Fix error --- lib/core/i18n/en.json | 3 +- .../components/media-player.component.html | 1 + .../components/media-player.component.spec.ts | 108 ++++++++++++++++++ .../components/media-player.component.ts | 17 ++- .../viewer/components/viewer.component.html | 1 + lib/core/viewer/models/viewer.model.ts | 23 ++++ lib/core/viewer/public-api.ts | 2 + lib/core/viewer/services/view-util.service.ts | 39 ++++++- 8 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 lib/core/viewer/components/media-player.component.spec.ts create mode 100644 lib/core/viewer/models/viewer.model.ts diff --git a/lib/core/i18n/en.json b/lib/core/i18n/en.json index ce5696e30d..f5b836f360 100644 --- a/lib/core/i18n/en.json +++ b/lib/core/i18n/en.json @@ -376,7 +376,8 @@ "CLOSE": "Close", "PLACEHOLDER": "Password", "ERROR": "Password is wrong" - } + }, + "SUBTITLES": "Subtitles" }, "ERROR_CONTENT": { "UNKNOWN": { diff --git a/lib/core/viewer/components/media-player.component.html b/lib/core/viewer/components/media-player.component.html index 0c825b9da0..9f53f25612 100644 --- a/lib/core/viewer/components/media-player.component.html +++ b/lib/core/viewer/components/media-player.component.html @@ -1,3 +1,4 @@ diff --git a/lib/core/viewer/components/media-player.component.spec.ts b/lib/core/viewer/components/media-player.component.spec.ts new file mode 100644 index 0000000000..55c0623fd3 --- /dev/null +++ b/lib/core/viewer/components/media-player.component.spec.ts @@ -0,0 +1,108 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SimpleChange, SimpleChanges } from '@angular/core'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { MediaPlayerComponent } from './media-player.component'; +import { setupTestBed } from '../../testing/setup-test-bed'; +import { CoreTestingModule } from '../../testing/core.testing.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { AlfrescoApiServiceMock } from '../../mock/alfresco-api.service.mock'; +import { AlfrescoApiService } from '../../services'; +import { NodeEntry } from '@alfresco/js-api'; + +describe('Test Media player component ', () => { + + let component: MediaPlayerComponent; + let fixture: ComponentFixture; + let alfrescoApiService: AlfrescoApiService; + let change: SimpleChanges; + + setupTestBed({ + imports: [ + TranslateModule.forRoot(), + CoreTestingModule + ], + providers: [ + { provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock } + ] + }); + + describe('Media tracks', () => { + + beforeEach(() => { + fixture = TestBed.createComponent(MediaPlayerComponent); + alfrescoApiService = TestBed.inject(AlfrescoApiService); + change = { nodeId: new SimpleChange(null, 'nodeId', true) }; + + component = fixture.componentInstance; + component.urlFile = 'http://fake.url'; + fixture.detectChanges(); + }); + + it('should generate tracks for media file when webvtt rendition exists', fakeAsync(() => { + const fakeRenditionUrl = 'http://fake.rendition.url'; + spyOn(alfrescoApiService.nodesApi, 'getNode').and.returnValues( + Promise.resolve(new NodeEntry({ entry: { name: 'file1', content: {} } })) + ); + spyOn(alfrescoApiService.renditionsApi, 'getRenditions').and.returnValues( + { list: { entries: [{ entry: { id: 'webvtt', status: 'CREATED' } }] } } + ); + spyOn(alfrescoApiService.contentApi, 'getContentUrl').and.returnValues('http://iam-fake.url'); + spyOn(alfrescoApiService.contentApi, 'getRenditionUrl').and.returnValue(fakeRenditionUrl); + + component.ngOnChanges(change); + tick(); + fixture.detectChanges(); + expect(component.tracks).toEqual([{ src: fakeRenditionUrl, kind: 'subtitles', label: 'ADF_VIEWER.SUBTITLES' }]); + })); + + it('should not generate tracks for media file when webvtt rendition is not created', fakeAsync(() => { + spyOn(alfrescoApiService.nodesApi, 'getNode').and.returnValues( + Promise.resolve(new NodeEntry({ entry: { name: 'file1', content: {} } })) + ); + + spyOn(alfrescoApiService.renditionsApi, 'getRenditions').and.returnValues( + { list: { entries: [{ entry: { id: 'webvtt', status: 'NOT_CREATED' } }] } } + ); + + spyOn(alfrescoApiService.contentApi, 'getContentUrl').and.returnValues('http://iam-fake.url'); + + component.ngOnChanges(change); + tick(); + fixture.detectChanges(); + expect(component.tracks.length).toBe(0); + })); + + it('should not generate tracks for media file when webvtt rendition does not exist', fakeAsync(() => { + spyOn(alfrescoApiService.nodesApi, 'getNode').and.returnValues( + Promise.resolve(new NodeEntry({ entry: { name: 'file1', content: {} } })) + ); + + spyOn(alfrescoApiService.renditionsApi, 'getRenditions').and.returnValues( + { list: { entries: [] } } + ); + + spyOn(alfrescoApiService.contentApi, 'getContentUrl').and.returnValues('http://iam-fake.url'); + + component.ngOnChanges(change); + tick(); + fixture.detectChanges(); + expect(component.tracks.length).toBe(0); + })); + }); +}); diff --git a/lib/core/viewer/components/media-player.component.ts b/lib/core/viewer/components/media-player.component.ts index 3d6cfc9321..8791f29a67 100644 --- a/lib/core/viewer/components/media-player.component.ts +++ b/lib/core/viewer/components/media-player.component.ts @@ -17,6 +17,8 @@ import { Component, Input, OnChanges, SimpleChanges, ViewEncapsulation, Output, EventEmitter } from '@angular/core'; import { ContentService } from '../../services/content.service'; +import { Track } from '../models/viewer.model'; +import { ViewUtilService } from '../services/view-util.service'; @Component({ selector: 'adf-media-player', @@ -39,18 +41,31 @@ export class MediaPlayerComponent implements OnChanges { @Input() nameFile: string; + @Input() + nodeId: string; + + @Input() + tracks: Track[] = []; + @Output() error = new EventEmitter(); - constructor(private contentService: ContentService ) {} + constructor(private contentService: ContentService, private viewUtils: ViewUtilService) { + } ngOnChanges(changes: SimpleChanges) { const blobFile = changes['blobFile']; + const nodeId = changes['nodeId']; + if (blobFile && blobFile.currentValue) { this.urlFile = this.contentService.createTrustedUrl(this.blobFile); return; } + if (nodeId && nodeId.currentValue) { + this.viewUtils.generateMediaTracks(this.nodeId).then((tracks) => this.tracks = tracks); + } + if (!this.urlFile && !this.blobFile) { throw new Error('Attribute urlFile or blobFile is required'); } diff --git a/lib/core/viewer/components/viewer.component.html b/lib/core/viewer/components/viewer.component.html index d3d4b98742..99cddb1879 100644 --- a/lib/core/viewer/components/viewer.component.html +++ b/lib/core/viewer/components/viewer.component.html @@ -225,6 +225,7 @@ = new Subject(); constructor(private apiService: AlfrescoApiService, - private logService: LogService) { + private logService: LogService, + private translateService: TranslationService) { } /** @@ -294,4 +303,30 @@ export class ViewUtilService { this.urlFileContentChange.next(urlFileContent); } + async generateMediaTracks(nodeId: string): Promise { + return this.isRenditionAvailable(nodeId, ViewUtilService.SUBTITLES_RENDITION_NAME) + .then((value) => { + const tracks = []; + if (value) { + tracks.push({ + kind: 'subtitles', + src: this.apiService.contentApi.getRenditionUrl(nodeId, ViewUtilService.SUBTITLES_RENDITION_NAME), + label: this.translateService.instant('ADF_VIEWER.SUBTITLES') + }); + } + return tracks; + }) + .catch((err) => { + this.logService.error('Error while retrieving ' + ViewUtilService.SUBTITLES_RENDITION_NAME + ' rendition'); + this.logService.error(err); + return []; + }); + } + + private async isRenditionAvailable(nodeId: string, renditionId: string): Promise { + const renditionPaging: RenditionPaging = await this.apiService.renditionsApi.getRenditions(nodeId); + const rendition: RenditionEntry = renditionPaging.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId); + + return rendition?.entry?.status?.toString() === 'CREATED' || false; + } }