forked from EVOgeek/Vmeda.Online
219 lines
9.3 KiB
TypeScript
219 lines
9.3 KiB
TypeScript
// (C) Copyright 2015 Martin Dougiamas
|
||
//
|
||
// 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 { Directive, Input, OnInit, ElementRef } from '@angular/core';
|
||
import { Platform } from 'ionic-angular';
|
||
import { CoreAppProvider } from '../providers/app';
|
||
import { CoreLoggerProvider } from '../providers/logger';
|
||
import { CoreFilepoolProvider } from '../providers/filepool';
|
||
import { CoreSitesProvider } from '../providers/sites';
|
||
import { CoreDomUtilsProvider } from '../providers/utils/dom';
|
||
import { CoreUrlUtilsProvider } from '../providers/utils/url';
|
||
|
||
/**
|
||
* Directive to handle external content.
|
||
*
|
||
* This directive should be used with any element that links to external content
|
||
* which we want to have available when the app is offline. Typically media and links.
|
||
*
|
||
* If a file is downloaded, its URL will be replaced by the local file URL.
|
||
*/
|
||
@Directive({
|
||
selector: '[core-external-content]'
|
||
})
|
||
export class CoreExternalContentDirective implements OnInit {
|
||
@Input() siteId?: string; // Site ID to use.
|
||
@Input() component?: string; // Component to link the file to.
|
||
@Input() componentId?: string|number; // Component ID to use in conjunction with the component.
|
||
|
||
protected element: HTMLElement;
|
||
protected logger;
|
||
|
||
constructor(element: ElementRef, logger: CoreLoggerProvider, private filepoolProvider: CoreFilepoolProvider,
|
||
private platform: Platform, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider,
|
||
private urlUtils: CoreUrlUtilsProvider, private appProvider: CoreAppProvider) {
|
||
// This directive can be added dynamically. In that case, the first param is the HTMLElement.
|
||
this.element = element.nativeElement || element;
|
||
this.logger = logger.getInstance('CoreExternalContentDirective');
|
||
}
|
||
|
||
/**
|
||
* Function executed when the component is initialized.
|
||
*/
|
||
ngOnInit() {
|
||
let currentSite = this.sitesProvider.getCurrentSite(),
|
||
siteId = this.siteId || (currentSite && currentSite.getId()),
|
||
targetAttr,
|
||
sourceAttr,
|
||
tagName = this.element.tagName;
|
||
|
||
if (tagName === 'A') {
|
||
targetAttr = 'href';
|
||
sourceAttr = 'href';
|
||
|
||
} else if (tagName === 'IMG') {
|
||
targetAttr = 'src';
|
||
sourceAttr = 'src';
|
||
|
||
} else if (tagName === 'AUDIO' || tagName === 'VIDEO' || tagName === 'SOURCE' || tagName === 'TRACK') {
|
||
targetAttr = 'src';
|
||
sourceAttr = 'targetSrc';
|
||
|
||
if (tagName === 'VIDEO') {
|
||
let poster = (<HTMLVideoElement>this.element).poster;
|
||
if (poster) {
|
||
// Handle poster.
|
||
this.handleExternalContent('poster', poster, siteId).catch(() => {
|
||
// Ignore errors.
|
||
});
|
||
}
|
||
}
|
||
|
||
} else {
|
||
// Unsupported tag.
|
||
this.logger.warn('Directive attached to non-supported tag: ' + tagName);
|
||
return;
|
||
}
|
||
|
||
let url = this.element.getAttribute(sourceAttr) || this.element.getAttribute(targetAttr);
|
||
this.handleExternalContent(targetAttr, url, siteId).catch(() => {
|
||
// Ignore errors.
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Add a new source with a certain URL as a sibling of the current element.
|
||
*
|
||
* @param {string} url URL to use in the source.
|
||
*/
|
||
protected addSource(url: string) : void {
|
||
if (this.element.tagName !== 'SOURCE') {
|
||
return;
|
||
}
|
||
|
||
let newSource = document.createElement('source'),
|
||
type = this.element.getAttribute('type');
|
||
|
||
newSource.setAttribute('src', url);
|
||
|
||
if (type) {
|
||
if (this.platform.is('android') && type == 'video/quicktime') {
|
||
// Fix for VideoJS/Chrome bug https://github.com/videojs/video.js/issues/423 .
|
||
newSource.setAttribute('type', 'video/mp4');
|
||
} else {
|
||
newSource.setAttribute('type', type);
|
||
}
|
||
}
|
||
this.element.parentNode.insertBefore(newSource, this.element);
|
||
}
|
||
|
||
/**
|
||
* Handle external content, setting the right URL.
|
||
*
|
||
* @param {string} targetAttr Attribute to modify.
|
||
* @param {string} url Original URL to treat.
|
||
* @param {string} [siteId] Site ID.
|
||
* @return {Promise<any>} Promise resolved if the element is successfully treated.
|
||
*/
|
||
protected handleExternalContent(targetAttr: string, url: string, siteId?: string) : Promise<any> {
|
||
|
||
const tagName = this.element.tagName;
|
||
|
||
if (tagName == 'VIDEO' && targetAttr != 'poster') {
|
||
let video = <HTMLVideoElement> this.element;
|
||
if (video.textTracks) {
|
||
// It's a video with subtitles. In iOS, subtitles position is wrong so it needs to be fixed.
|
||
video.textTracks.onaddtrack = (event) => {
|
||
let track = <TextTrack> event.track;
|
||
if (track) {
|
||
track.oncuechange = () => {
|
||
var line = this.platform.is('tablet') || this.platform.is('android') ? 90 : 80;
|
||
// Position all subtitles to a percentage of video height.
|
||
Array.from(track.cues).forEach((cue: any) => {
|
||
cue.snapToLines = false;
|
||
cue.line = line;
|
||
cue.size = 100; // This solves some Android issue.
|
||
});
|
||
// Delete listener.
|
||
track.oncuechange = null;
|
||
};
|
||
}
|
||
};
|
||
}
|
||
|
||
}
|
||
|
||
if (!url || !url.match(/^https?:\/\//i) || (tagName === 'A' && !this.urlUtils.isDownloadableUrl(url))) {
|
||
this.logger.debug('Ignoring non-downloadable URL: ' + url);
|
||
if (tagName === 'SOURCE') {
|
||
// Restoring original src.
|
||
this.addSource(url);
|
||
}
|
||
return Promise.reject(null);
|
||
}
|
||
|
||
// Get the webservice pluginfile URL, we ignore failures here.
|
||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||
if (!site.canDownloadFiles() && this.urlUtils.isPluginFileUrl(url)) {
|
||
this.element.parentElement.removeChild(this.element); // Remove element since it'll be broken.
|
||
return Promise.reject(null);
|
||
}
|
||
|
||
// Download images, tracks and posters if size is unknown.
|
||
let promise,
|
||
dwnUnknown = tagName == 'IMG' || tagName == 'TRACK' || targetAttr == 'poster';
|
||
|
||
if (targetAttr === 'src' && tagName !== 'SOURCE' && tagName !== 'TRACK') {
|
||
promise = this.filepoolProvider.getSrcByUrl(siteId, url, this.component, this.componentId, 0, true, dwnUnknown);
|
||
} else {
|
||
promise = this.filepoolProvider.getUrlByUrl(siteId, url, this.component, this.componentId, 0, true, dwnUnknown);
|
||
}
|
||
|
||
return promise.then((finalUrl) => {
|
||
this.logger.debug('Using URL ' + finalUrl + ' for ' + url);
|
||
if (tagName === 'SOURCE') {
|
||
// The browser does not catch changes in SRC, we need to add a new source.
|
||
// @todo: Check if changing src works in Android 4.4, maybe the problem was only in 4.1-4.3.
|
||
this.addSource(finalUrl);
|
||
} else {
|
||
this.element.setAttribute(targetAttr, finalUrl);
|
||
}
|
||
|
||
// Set events to download big files (not downloaded automatically).
|
||
if (finalUrl.indexOf('http') === 0 && targetAttr != 'poster' &&
|
||
(tagName == 'VIDEO' || tagName == 'AUDIO' || tagName == 'A' || tagName == 'SOURCE')) {
|
||
let eventName = tagName == 'A' ? 'click' : 'play',
|
||
clickableEl = this.element;
|
||
|
||
if (tagName == 'SOURCE') {
|
||
clickableEl = <HTMLElement> this.domUtils.closest(this.element, 'video,audio');
|
||
if (!clickableEl) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
clickableEl.addEventListener(eventName, () => {
|
||
// User played media or opened a downloadable link.
|
||
// Download the file if in wifi and it hasn't been downloaded already (for big files).
|
||
if (!this.appProvider.isNetworkAccessLimited()) {
|
||
// We aren't using the result, so it doesn't matter which of the 2 functions we call.
|
||
this.filepoolProvider.getUrlByUrl(siteId, url, this.component, this.componentId, 0, false);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
});
|
||
}
|
||
}
|