import { constructors } from '../core/library.js';
import { generateUniqueString, xt, λthis, λnull } from '../core/utilities.js';
import baseMix from '../mixin/base.js';
import assetMix from '../mixin/asset.js';The factory generates wrapper Objects around <video> elements which can either be pulled from the current document (DOM-based assets) or fetched from the server using an URL address.
Scrawl-canvas can also create VideoAssets from the Web API MediaStream interface - this is an experimental feature.
VideoAssets are used by Picture and Grid entitys, and Pattern styles (though not recommended).
import { constructors } from '../core/library.js';
import { generateUniqueString, xt, λthis, λnull } from '../core/utilities.js';
import baseMix from '../mixin/base.js';
import assetMix from '../mixin/asset.js';const VideoAsset = function (items = {}) {
return this.assetConstructor(items);
};let P = VideoAsset.prototype = Object.create(Object.prototype);
P.type = 'Video';
P.lib = 'asset';
P.isArtefact = false;
P.isAsset = true;P = baseMix(P);
P = assetMix(P);No additional attributes required beyond those supplied by the mixins
Assets do not take part in the packet or clone systems; they can, however, be used for importing and actioning packets as they retain those base functions
P.saveAsPacket = function () {
return [this.name, this.type, this.lib, {}];
};
P.stringifyFunction = λnull;
P.processPacketOut = λnull;
P.finalizePacketOut = λnull;P.clone = λthis;let G = P.getters,
S = P.setters,
D = P.deltaSetters;source
S.source = function (item = {}) {
if (item) {
if (item.tagName.toUpperCase() === 'VIDEO') {
this.source = item;
this.sourceNaturalWidth = item.videoWidth || 0;
this.sourceNaturalHeight = item.videoHeight || 0;
this.sourceLoaded = (item.readyState > 2) ? true : false;
}
if (this.sourceLoaded) this.notifySubscribers();
}
};checkSource
P.checkSource = function (width, height) {
let source = this.source;
if (source && source.readyState > 2) {
this.sourceLoaded = true;
if (this.sourceNaturalWidth !== source.videoWidth ||
this.sourceNaturalHeight !== source.videoHeight ||
this.sourceNaturalWidth !== width ||
this.sourceNaturalHeight !== height) {
this.sourceNaturalWidth = source.videoWidth;
this.sourceNaturalHeight = source.videoHeight;
this.notifySubscribers();
}
}
else this.sourceLoaded = false;
};The following functions invoke their namesake functions on the source <video> element.
addTextTrack
P.addTextTrack = function (kind, label, language) {
let source = this.source;
if (source && source.addTextTrack) source.addTextTrack(kind, label, language);
};captureStream
P.captureStream = function () {
let source = this.source;
if (source && source.captureStream) return source.captureStream();
else return false;
};canPlayType
P.canPlayType = function (mytype) {
let source = this.source;
if (source) return source.canPlayType(mytype);
else return 'maybe';
};fastSeek
P.fastSeek = function (time) {
let source = this.source;
if (source && source.fastSeek) source.fastSeek(time);
};load
P.load = function () {
let source = this.source;
if (source) source.load();
};pause
P.pause = function () {
let source = this.source;
if (source) source.pause();
};play
P.play = function () {
let source = this.source;
if (source) return source.play().catch((e) => console.log(e.code, e.name, e.message));
else return Promise.reject('Source not defined');
};setMediaKeys
P.setMediaKeys = function (keys) {
let source = this.source;
if (source) {
if (source.setMediaKeys) return source.setMediaKeys(keys);
else return Promise.reject('setMediaKeys not supported');
}
else return Promise.reject('Source not defined');
};setSinkId
P.setSinkId = function () {
let source = this.source;
if (source) {
if (source.setSinkId) return source.setSinkId();
else return Promise.reject('setSinkId not supported');
}
else return Promise.reject('Source not defined');
};gettableVideoAssetAtributes, settableVideoAssetAtributes - exported Arrays.
const gettableVideoAssetAtributes = [
'video_audioTracks',
'video_autoPlay',
'video_buffered',
'video_controller',
'video_controls',
'video_controlsList',
'video_crossOrigin',
'video_currentSrc',
'video_currentTime',
'video_defaultMuted',
'video_defaultPlaybackRate',
'video_disableRemotePlayback',
'video_duration',
'video_ended',
'video_error',
'video_loop',
'video_mediaGroup',
'video_mediaKeys',
'video_muted',
'video_networkState',
'video_paused',
'video_playbackRate',
'video_readyState',
'video_seekable',
'video_seeking',
'video_sinkId',
'video_src',
'video_srcObject',
'video_textTracks',
'video_videoTracks',
'video_volume',
];
const settableVideoAssetAtributes = [
'video_autoPlay',
'video_controller',
'video_controls',
'video_crossOrigin',
'video_currentTime',
'video_defaultMuted',
'video_defaultPlaybackRate',
'video_disableRemotePlayback',
'video_loop',
'video_mediaGroup',
'video_muted',
'video_playbackRate',
'video_src',
'video_srcObject',
'video_volume',
];importDomVideo - import videos defined in the web page HTML code
display: none;, opacity: 0;, etc.const importDomVideo = function (query) {
let reg = /.*\/(.*?)\./;
let items = document.querySelectorAll(query);
items.forEach(item => {
let name;
if (item.tagName.toUpperCase() === 'VIDEO') {
if (item.id || item.name) name = item.id || item.name;
else {
let match = reg.exec(item.src);
name = (match && match[1]) ? match[1] : '';
}
let vid = makeVideoAsset({
name: name,
source: item,
});
if (item.readyState <= 2) {
item.oncanplay = () => {
vid.set({
source: item,
});
};
}
}
});
};importMediaStream - Warning: experimental!
const importMediaStream = function (items = {}) {Setup the constraints object with user-supplied data in the items argument
let constraints = {};For proof-of-concept, only interested in wheter to include or exclude audio in the capture
constraints.audio = (xt(items.audio)) ? items.audio : true;For video, limiting functionality to accepting user values for video width and height (as minDIMENSION, maxDIMENSION and the ideal DIMENSION, and a preference for which camera to use - where applicable
constraints.video = {};
let width = constraints.video.width = {};
if (items.minWidth) width.min = items.minWidth;
if (items.maxWidth) width.max = items.maxWidth;
width.ideal = (items.width) ? items.width : 1280;
let height = constraints.video.height = {};
if (items.minHeight) height.min = items.minHeight;
if (items.maxHeight) height.max = items.maxHeight;
height.ideal = (items.height) ? items.height : 720;For mobile devices etc - values can be ‘user’ or ‘environment’
if (items.facing) constraints.video.facingMode = items.facing;We need a video element to receive the media stream
let name = items.name || generateUniqueString();
let el = document.createElement('video');
let vid = makeVideoAsset({
name: name,
source: el,
});
return new Promise((resolve, reject) => {
if (navigator && navigator.mediaDevices) {
navigator.mediaDevices.getUserMedia(constraints)
.then(mediaStream => {
let actuals = mediaStream.getVideoTracks(),
data;
if (Array.isArray(actuals) && actuals[0]) data = actuals[0].getConstraints();
el.id = vid.name;
if (data) {
el.width = data.width;
el.height = data.height;
}
el.srcObject = mediaStream;
el.onloadedmetadata = function (e) {
el.play();
}
resolve(vid);
})
.catch (err => {
console.log(err.message);
resolve(vid);
});
}
else reject('Navigator.mediaDevices object not found');
});
};importVideo - load videos from a remote server and create assets from them
Arguments can be a comma-separated list of String urls. For example, for a video link at server url http://www.example.com/path/to/image/flower.mp4:
Alternatively, the arguments can include an object with the following attributes:
Note: strings and object arguments can be mixed - Scrawl-canvas will interrrogate each argument in turn and take appropriate action to load the assets.
Using videos from 3rd Party cloud servers - for example, YouTube. DON’T. Services such as YouTube generally require users to embed videos into web pages using their video player technology. This is so page visitors can be served adverts and recommended videos, etc. Attempts to circumvent this functionality will often break the 3rd Party’s Terms of Service.
const importVideo = function (...args) {
let reg = /.*\/(.*?)\./,
result = '';
if (args.length) {
let name, className, visibility, parent, sources, preload;
let flag = false;
let firstArg = args[0];one or more string urls has been passed to the function
if (firstArg.substring) {
let match = reg.exec(firstArg);
name = (match && match[1]) ? match[1] : '';
sources = [...args];
className = '';
visibility = false;
parent = null;
preload = 'auto';
flag = true;
}a single object has been passed to the function
else if (firstArg && firstArg.src) {
name = firstArg.name || '';
sources = [...firstArg.src];
className = firstArg.className || '';
visibility = firstArg.visibility || false;
parent = document.querySelector(parent);
preload = firstArg.preload || 'auto';
flag = true;
}build the video element
let video = makeVideoAsset({
name: name,
});
if (flag) {
let vid = document.createElement('video');
vid.name = name;
vid.className = className;
vid.style.display = (visibility) ? 'block' : 'none';
vid.crossOrigin = 'anonymous';
vid.preload = preload;
sources.forEach(item => {
let el = document.createElement('source');
el.src = item;
vid.appendChild(el);
});
vid.onload = () => {
video.set({
source: vid,
});
if (parent) parent.appendChild(vid);
};
video.set({
source: vid,
});
result = name;
}
}
return result;
};const makeVideoAsset = function (items) {
return new VideoAsset(items);
};
constructors.VideoAsset = VideoAsset;export {
makeVideoAsset,
gettableVideoAssetAtributes,
settableVideoAssetAtributes,
importDomVideo,
importVideo,
importMediaStream,
};