Improved video embed handling.
This commit is contained in:
parent
48ce465893
commit
e1122fb6b9
7 changed files with 1128 additions and 336 deletions
|
@ -1,6 +1,7 @@
|
|||
.embed {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-shadow: initial;
|
||||
}
|
||||
|
||||
.embed iframe {
|
||||
|
@ -15,6 +16,7 @@
|
|||
cursor: pointer;
|
||||
color: var(--text-colour);
|
||||
text-decoration: none;
|
||||
text-shadow: initial;
|
||||
}
|
||||
.embedph:hover .embedph-bg img,
|
||||
.embedph:active .embedph-bg img,
|
||||
|
@ -163,3 +165,42 @@
|
|||
max-width: 640px;
|
||||
max-height: 360px;
|
||||
}
|
||||
|
||||
.embedvf {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
}
|
||||
.embedvf:hover .embedvf-controls,
|
||||
.embedvf:focus .embedvf-controls,
|
||||
.embedvf:active .embedvf-controls,
|
||||
.embedvf:focus-within .embedvf-controls {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
.embedvf-player {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.embedvf-overlay {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.embedvf-controls {
|
||||
pointer-events: initial;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
padding: 5px;
|
||||
background-color: var(--background-colour-translucent-7);
|
||||
border-radius: 5px;
|
||||
opacity: 0;
|
||||
transform: scale(.95);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
transition: opacity .2s, transform .2s;
|
||||
}
|
||||
|
|
|
@ -2,11 +2,14 @@ var Misuzu = function() {
|
|||
timeago.render($qa('time'));
|
||||
hljs.initHighlighting();
|
||||
|
||||
MszEmbed.init(location.protocol + '//uiharu.' + location.host);
|
||||
|
||||
Misuzu.initQuickSubmit(); // only used by the forum posting form
|
||||
Misuzu.Forum.Editor.init();
|
||||
Misuzu.Events.dispatch();
|
||||
Misuzu.initLoginPage();
|
||||
Misuzu.handleEmbeds();
|
||||
|
||||
MszEmbed.handle($qa('.js-msz-embed-media'));
|
||||
};
|
||||
Misuzu.showMessageBox = function(text, title, buttons) {
|
||||
if($q('.messagebox'))
|
||||
|
@ -122,336 +125,3 @@ Misuzu.initQuickSubmit = function() {
|
|||
}
|
||||
});
|
||||
};
|
||||
Misuzu.handleEmbeds = function() {
|
||||
const UIHARU_API = location.protocol + '//uiharu.' + location.host;
|
||||
|
||||
const embeds = Array.from($qa('.js-msz-embed-media'));
|
||||
if(embeds.length > 0) {
|
||||
$as(embeds);
|
||||
|
||||
const uiharu = new Uiharu(UIHARU_API),
|
||||
elems = new Map;
|
||||
|
||||
for(const elem of embeds) {
|
||||
let cleanUrl = elem.dataset.mszEmbedUrl.replace(/ /, '%20');
|
||||
if(cleanUrl.indexOf('http://') !== 0 && cleanUrl.indexOf('https://') !== 0) {
|
||||
elem.textContent = elem.dataset.mszEmbedUrl;
|
||||
continue;
|
||||
}
|
||||
|
||||
elem.textContent = 'Loading...';
|
||||
|
||||
if(elems.has(cleanUrl))
|
||||
elems.get(cleanUrl).push(elem);
|
||||
else
|
||||
elems.set(cleanUrl, [elem]);
|
||||
}
|
||||
|
||||
uiharu.lookupMany(Array.from(elems.keys()), function(resp) {
|
||||
if(resp.results === undefined)
|
||||
return; // rip
|
||||
|
||||
for(const result of resp.results) {
|
||||
let elemList = elems.get(result.url);
|
||||
|
||||
const replaceWithUrl = function() {
|
||||
for(let i = 0; i < elemList.length; ++i) {
|
||||
let body = $e({
|
||||
tag: 'a',
|
||||
attrs: {
|
||||
className: 'link',
|
||||
href: result.url,
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
},
|
||||
child: result.url
|
||||
});
|
||||
$ib(elemList[i], body);
|
||||
$r(elemList[i]);
|
||||
elemList[i] = body;
|
||||
}
|
||||
};
|
||||
|
||||
if(result.error) {
|
||||
replaceWithUrl();
|
||||
console.error(result.error);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(result.info.title === undefined) {
|
||||
replaceWithUrl();
|
||||
console.warn('Media is no longer available.');
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
(function(elemList, info) {
|
||||
const replaceElement = function(bodyInfo) {
|
||||
for(let i = 0; i < elemList.length; ++i) {
|
||||
let body = $e(bodyInfo);
|
||||
$ib(elemList[i], body);
|
||||
$r(elemList[i]);
|
||||
elemList[i] = body;
|
||||
}
|
||||
};
|
||||
|
||||
const createEmbedPH = function(type, info, onclick, width, height) {
|
||||
let infoChildren = [];
|
||||
|
||||
infoChildren.push({
|
||||
tag: 'h1',
|
||||
attrs: {
|
||||
className: 'embedph-info-title',
|
||||
},
|
||||
child: info.title,
|
||||
});
|
||||
|
||||
if(info.description) {
|
||||
let firstLine = info.description.split("\n")[0].trim();
|
||||
if(firstLine.length > 300)
|
||||
firstLine = firstLine.substring(0, 300).trim() + '...';
|
||||
|
||||
infoChildren.push({
|
||||
tag: 'div',
|
||||
attrs: {
|
||||
className: 'embedph-info-desc',
|
||||
},
|
||||
child: firstLine,
|
||||
});
|
||||
}
|
||||
|
||||
infoChildren.push({
|
||||
tag: 'div',
|
||||
attrs: {
|
||||
className: 'embedph-info-site',
|
||||
},
|
||||
child: info.site_name,
|
||||
});
|
||||
|
||||
let style = info.color === undefined ? '' : ('--embedph-colour: ' + info.color);
|
||||
|
||||
if(width !== undefined)
|
||||
style += 'width: ' + width.toString() + ';';
|
||||
if(height !== undefined)
|
||||
style += 'height: ' + height.toString() + ';';
|
||||
|
||||
let bgElem;
|
||||
if(info.image !== undefined) {
|
||||
bgElem = {
|
||||
tag: 'img',
|
||||
attrs: {
|
||||
src: info.image,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
bgElem = {};
|
||||
}
|
||||
|
||||
return {
|
||||
attrs: {
|
||||
href: 'javascript:void(0);',
|
||||
className: ('embedph embedph-' + type),
|
||||
style: style,
|
||||
},
|
||||
child: [
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedph-bg',
|
||||
},
|
||||
child: bgElem,
|
||||
},
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedph-fg',
|
||||
},
|
||||
child: [
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedph-info',
|
||||
},
|
||||
child: {
|
||||
attrs: {
|
||||
className: 'embedph-info-wrap',
|
||||
},
|
||||
child: [
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedph-info-bar',
|
||||
},
|
||||
},
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedph-info-body',
|
||||
},
|
||||
child: infoChildren,
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedph-play',
|
||||
onclick: function(ev) {
|
||||
if(ev.target.tagName.toLowerCase() !== 'a')
|
||||
onclick(ev);
|
||||
},
|
||||
},
|
||||
child: [
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedph-play-internal',
|
||||
},
|
||||
child: {
|
||||
tag: 'i',
|
||||
attrs: {
|
||||
className: 'fas fa-play fa-4x fa-fw',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'a',
|
||||
attrs: {
|
||||
className: 'embedph-play-external',
|
||||
href: info.url,
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
},
|
||||
child: ('or watch on ' + info.site_name + '?'),
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
if(info.type === 'youtube:video') {
|
||||
let embedUrl = 'https://www.youtube.com/embed/' + info.youtube_video_id + '?rel=0&autoplay=1';
|
||||
|
||||
if(info.youtube_start_time)
|
||||
embedUrl += '&t=' + encodeURIComponent(info.youtube_start_time);
|
||||
|
||||
if(info.youtube_playlist) {
|
||||
embedUrl += '&list=' + encodeURIComponent(info.youtube_playlist);
|
||||
|
||||
if(info.youtube_playlist_index)
|
||||
embedUrl += '&index=' + encodeURIComponent(info.youtube_playlist_index);
|
||||
}
|
||||
|
||||
replaceElement(createEmbedPH('youtube', info, function() {
|
||||
replaceElement({
|
||||
attrs: {
|
||||
className: 'embed embed-youtube',
|
||||
},
|
||||
child: {
|
||||
tag: 'iframe',
|
||||
attrs: {
|
||||
frameborder: 0,
|
||||
allow: 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture',
|
||||
allowfullscreen: 'allowfullscreen',
|
||||
src: embedUrl,
|
||||
},
|
||||
},
|
||||
});
|
||||
}));
|
||||
} else if(info.type === 'niconico:video') {
|
||||
let embedUrl = 'https://embed.nicovideo.jp/watch/' + info.nicovideo_video_id + '/script?w=100%25&h=100%25&autoplay=1';
|
||||
|
||||
if(info.nicovideo_start_time)
|
||||
embedUrl += '&from=' + encodeURIComponent(info.nicovideo_start_time);
|
||||
|
||||
replaceElement(createEmbedPH('nicovideo', info, function() {
|
||||
replaceElement({
|
||||
attrs: {
|
||||
className: 'embed embed-nicovideo',
|
||||
},
|
||||
child: {
|
||||
tag: 'script',
|
||||
attrs: {
|
||||
async: 'async',
|
||||
src: embedUrl,
|
||||
},
|
||||
},
|
||||
});
|
||||
}));
|
||||
} else if(info.type === 'media') {
|
||||
// todo: proxying
|
||||
// think uiharu will just serve as the camo system
|
||||
if(info.is_video) {
|
||||
let width = info.width,
|
||||
height = info.height;
|
||||
|
||||
const gcd = function(a, b) {
|
||||
return (b == 0) ? a : gcd(b, a % b);
|
||||
};
|
||||
|
||||
let ratio = gcd(width, height),
|
||||
widthRatio = width / ratio,
|
||||
heightRatio = height / ratio;
|
||||
|
||||
if(width > height) {
|
||||
width = Math.min(640, width);
|
||||
height = Math.ceil((width / widthRatio) * heightRatio).toString() + 'px';
|
||||
width = width.toString() + 'px';
|
||||
} else {
|
||||
height = Math.min(360, height);
|
||||
width = Math.ceil((height / heightRatio) * widthRatio).toString() + 'px';
|
||||
height = height.toString() + 'px';
|
||||
}
|
||||
|
||||
replaceElement(createEmbedPH('external', info, function() {
|
||||
replaceElement({
|
||||
attrs: {
|
||||
className: 'embed embed-external',
|
||||
},
|
||||
child: {
|
||||
tag: 'video',
|
||||
attrs: {
|
||||
autoplay: 'autoplay',
|
||||
controls: 'controls',
|
||||
src: info.url,
|
||||
style: {
|
||||
width: width,
|
||||
height: height,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}, width, height));
|
||||
} else if(info.is_audio) {
|
||||
// need dedicated audio placeholder and a player frame that includes the cover art
|
||||
replaceElement(createEmbedPH('external', info, function() {
|
||||
replaceElement({
|
||||
attrs: {
|
||||
className: 'embed embed-external',
|
||||
},
|
||||
child: {
|
||||
tag: 'audio',
|
||||
attrs: {
|
||||
autoplay: 'autoplay',
|
||||
controls: 'controls',
|
||||
src: info.url,
|
||||
},
|
||||
},
|
||||
});
|
||||
}, '300px', '300px'));
|
||||
} else if(info.is_image) {
|
||||
replaceElement({
|
||||
tag: 'img',
|
||||
attrs: {
|
||||
src: info.url,
|
||||
alt: info.url,
|
||||
style: {
|
||||
maxWidth: '100%',
|
||||
maxHeight: '900px',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
})(elemList, result.info);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
117
assets/js/misuzu/embed.js
Normal file
117
assets/js/misuzu/embed.js
Normal file
|
@ -0,0 +1,117 @@
|
|||
var MszEmbed = (function() {
|
||||
let uiharu = undefined;
|
||||
|
||||
return {
|
||||
init: function(endPoint) {
|
||||
uiharu = new Uiharu(endPoint);
|
||||
},
|
||||
handle: function(targets) {
|
||||
if(!Array.isArray(targets))
|
||||
targets = Array.from(targets);
|
||||
|
||||
const filtered = new Map;
|
||||
for(const target of targets) {
|
||||
if(!(target instanceof HTMLElement)
|
||||
|| !('dataset' in target)
|
||||
|| !('mszEmbedUrl' in target.dataset))
|
||||
continue;
|
||||
|
||||
const cleanUrl = target.dataset.mszEmbedUrl.replace(/ /, '%20');
|
||||
if(cleanUrl.indexOf('https://') !== 0
|
||||
&& cleanUrl.indexOf('http://') !== 0) {
|
||||
target.textContent = target.dataset.mszEmbedUrl;
|
||||
continue;
|
||||
}
|
||||
|
||||
$rc(target);
|
||||
target.appendChild($e({
|
||||
tag: 'i',
|
||||
attrs: {
|
||||
className: 'fas fa-2x fa-spinner fa-pulse',
|
||||
style: {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
lineHeight: '32px',
|
||||
textAlign: 'center',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
if(filtered.has(cleanUrl))
|
||||
filtered.get(cleanUrl).push(target);
|
||||
else
|
||||
filtered.set(cleanUrl, [target]);
|
||||
}
|
||||
|
||||
const replaceWithUrl = function(targets, url) {
|
||||
for(const target of targets) {
|
||||
let body = $e({
|
||||
tag: 'a',
|
||||
attrs: {
|
||||
className: 'link',
|
||||
href: url,
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
},
|
||||
child: url
|
||||
});
|
||||
$ib(target, body);
|
||||
$r(target);
|
||||
}
|
||||
};
|
||||
|
||||
filtered.forEach(function(targets, url) {
|
||||
uiharu.lookupOne(url, function(metadata) {
|
||||
if(metadata.error) {
|
||||
replaceWithUrl(targets, url);
|
||||
console.error(metadata.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if(metadata.title === undefined) {
|
||||
replaceWithUrl(targets, url);
|
||||
console.warn('Media is no longer available.');
|
||||
return;
|
||||
}
|
||||
|
||||
let phc = undefined,
|
||||
options = {
|
||||
onembed: console.log,
|
||||
};
|
||||
|
||||
if(metadata.type === 'youtube:video') {
|
||||
phc = MszVideoEmbedPlaceholder;
|
||||
options.type = 'youtube';
|
||||
options.player = MszVideoEmbedYouTube;
|
||||
} else if(metadata.type === 'niconico:video') {
|
||||
phc = MszVideoEmbedPlaceholder;
|
||||
options.type = 'nicovideo';
|
||||
options.player = MszVideoEmbedNicoNico;
|
||||
} else if(metadata.is_video) {
|
||||
phc = MszVideoEmbedPlaceholder;
|
||||
options.type = 'external';
|
||||
options.player = MszVideoEmbedPlayer;
|
||||
//options.frame = MszVideoEmbedFrame;
|
||||
options.nativeControls = true;
|
||||
options.autosize = false;
|
||||
options.maxWidth = 640;
|
||||
options.maxHeight = 360;
|
||||
} else if(metadata.is_audio) {
|
||||
options.type = 'external';
|
||||
} else if(metadata.is_image) {
|
||||
options.type = 'external';
|
||||
}
|
||||
|
||||
if(phc === undefined)
|
||||
return;
|
||||
|
||||
for(const target of targets) {
|
||||
const placeholder = new phc(metadata, options);
|
||||
if(placeholder !== undefined)
|
||||
placeholder.replaceElement(target);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
})();
|
|
@ -64,7 +64,7 @@ Misuzu.Forum.Editor.init = function() {
|
|||
|
||||
lastPostParser = postParser;
|
||||
postingPreview.innerHTML = text;
|
||||
Misuzu.handleEmbeds();
|
||||
MszEmbed.handle($qa('.js-msz-embed-media'));
|
||||
|
||||
previewButton.removeAttribute('disabled');
|
||||
postingParser.removeAttribute('disabled');
|
||||
|
@ -113,7 +113,7 @@ Misuzu.Forum.Editor.init = function() {
|
|||
lastPostText = postText;
|
||||
lastPostParser = postParser;
|
||||
postingPreview.innerHTML = text;
|
||||
Misuzu.handleEmbeds();
|
||||
MszEmbed.handle($qa('.js-msz-embed-media'));
|
||||
|
||||
postingPreview.removeAttribute('hidden');
|
||||
postingText.setAttribute('hidden', 'hidden');
|
||||
|
|
38
assets/js/misuzu/rng.js
Normal file
38
assets/js/misuzu/rng.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
const MszRandomInt = function(min, max) {
|
||||
let ret = 0;
|
||||
const range = max - min;
|
||||
|
||||
const bitsNeeded = Math.ceil(Math.log2(range));
|
||||
if(bitsNeeded > 53)
|
||||
return -1;
|
||||
|
||||
const bytesNeeded = Math.ceil(bitsNeeded / 8),
|
||||
mask = Math.pow(2, bitsNeeded) - 1;
|
||||
|
||||
const bytes = new Uint8Array(bytesNeeded);
|
||||
crypto.getRandomValues(bytes);
|
||||
|
||||
let p = (bytesNeeded - 1) * 8;
|
||||
for(let i = 0; i < bytesNeeded; ++i) {
|
||||
ret += bytes[i] * Math.pow(2, p);
|
||||
p -= 8;
|
||||
}
|
||||
|
||||
ret &= mask;
|
||||
|
||||
if(ret >= range)
|
||||
return MszRandomInt(min, max);
|
||||
|
||||
return min + ret;
|
||||
};
|
||||
|
||||
const MszUniqueStr = (function() {
|
||||
const chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789';
|
||||
|
||||
return function(length) {
|
||||
let str = '';
|
||||
for(let i = 0; i < length; ++i)
|
||||
str += chars[MszRandomInt(0, chars.length)];
|
||||
return str;
|
||||
};
|
||||
})();
|
843
assets/js/misuzu/vembed.js
Normal file
843
assets/js/misuzu/vembed.js
Normal file
|
@ -0,0 +1,843 @@
|
|||
const MszVideoEmbedPlayerEvents = function() {
|
||||
return [
|
||||
'play', 'pause', 'stop',
|
||||
'mute', 'volume',
|
||||
'rate', 'duration', 'time',
|
||||
];
|
||||
};
|
||||
|
||||
const MszVideoEstimateAspectRatio = function(width, height) {
|
||||
const gcd = function(a, b) { return b ? gcd(b, a % b) : Math.abs(a); };
|
||||
|
||||
const ratio = gcd(width, height);
|
||||
width /= ratio;
|
||||
height /= ratio;
|
||||
|
||||
return [width, height];
|
||||
};
|
||||
|
||||
const MszVideoConstrainSize = function(w, h, mw, mh) {
|
||||
mw = mw || 0;
|
||||
mh = mh || 0;
|
||||
w = w || 0;
|
||||
h = h || 0;
|
||||
|
||||
const ar = MszVideoEstimateAspectRatio(w, h);
|
||||
|
||||
if(w > h) {
|
||||
if(mw > 0) {
|
||||
w = Math.min(mw, w);
|
||||
h = Math.ceil((w / ar[0]) * ar[1]);
|
||||
if(mh > 0) h = Math.min(mh, h);
|
||||
}
|
||||
} else {
|
||||
if(mh > 0) {
|
||||
h = Math.min(mh, h);
|
||||
w = Math.ceil((h / ar[1]) * ar[0]);
|
||||
if(mw > 0) w = Math.min(mw, w);
|
||||
}
|
||||
}
|
||||
|
||||
return [w, h];
|
||||
};
|
||||
|
||||
const MszVideoEmbed = function(playerOrFrame) {
|
||||
const frame = playerOrFrame;
|
||||
const player = 'getPlayer' in frame ? frame.getPlayer() : frame;
|
||||
|
||||
const elem = $e({
|
||||
attrs: {
|
||||
classList: ['embed', 'embed-' + player.getType()],
|
||||
},
|
||||
child: frame,
|
||||
});
|
||||
|
||||
return {
|
||||
getElement: function() {
|
||||
return elem;
|
||||
},
|
||||
appendTo: function(target) {
|
||||
target.appendChild(elem);
|
||||
},
|
||||
insertBefore: function(ref) {
|
||||
$ib(ref, elem);
|
||||
},
|
||||
nuke: function() {
|
||||
$r(elem);
|
||||
},
|
||||
replaceElement(target) {
|
||||
$ib(target, elem);
|
||||
$r(target);
|
||||
},
|
||||
getFrame: function() {
|
||||
return frame;
|
||||
},
|
||||
getPlayer: function() {
|
||||
return player;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const MszVideoEmbedFrame = function(player, options) {
|
||||
options = options || {};
|
||||
|
||||
const elem = $e({
|
||||
attrs: {
|
||||
className: 'embedvf',
|
||||
style: {
|
||||
width: player.getWidth().toString() + 'px',
|
||||
height: player.getHeight().toString() + 'px',
|
||||
},
|
||||
},
|
||||
child: [
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedvf-player',
|
||||
},
|
||||
child: player,
|
||||
},
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedvf-overlay',
|
||||
},
|
||||
child: [
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedvf-controls',
|
||||
},
|
||||
child: [
|
||||
'Play/Pause Stop [|---------] 1.00x 100%',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
getElement: function() {
|
||||
return elem;
|
||||
},
|
||||
appendTo: function(target) {
|
||||
target.appendChild(elem);
|
||||
},
|
||||
insertBefore: function(ref) {
|
||||
$ib(ref, elem);
|
||||
},
|
||||
nuke: function() {
|
||||
$r(elem);
|
||||
},
|
||||
replaceElement(target) {
|
||||
$ib(target, elem);
|
||||
$r(target);
|
||||
},
|
||||
getPlayer: function() {
|
||||
return player;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const MszVideoEmbedPlayer = function(metadata, options) {
|
||||
options = options || {};
|
||||
|
||||
const shouldAutoplay = options.autoplay === undefined || options.autoplay,
|
||||
haveNativeControls = options.nativeControls !== undefined && options.nativeControls,
|
||||
shouldObserveResize = options.observeResize === undefined || options.observeResize;
|
||||
|
||||
const videoAttrs = {
|
||||
src: metadata.url,
|
||||
style: {},
|
||||
};
|
||||
|
||||
if(shouldAutoplay)
|
||||
videoAttrs.autoplay = 'autoplay';
|
||||
if(haveNativeControls)
|
||||
videoAttrs.controls = 'controls';
|
||||
|
||||
const constrainSize = function(w, h, mw, mh) {
|
||||
return MszVideoConstrainSize(w, h, mw || options.maxWidth, mh || options.maxHeight);
|
||||
};
|
||||
|
||||
const initialSize = constrainSize(
|
||||
options.width || metadata.width || 200,
|
||||
options.height || metadata.height || 200
|
||||
);
|
||||
|
||||
videoAttrs.style.width = initialSize[0].toString() + 'px';
|
||||
videoAttrs.style.height = initialSize[1].toString() + 'px';
|
||||
|
||||
const watchers = new MszWatcherCollection;
|
||||
watchers.define(MszVideoEmbedPlayerEvents());
|
||||
|
||||
const player = $e({
|
||||
tag: 'video',
|
||||
attrs: videoAttrs,
|
||||
});
|
||||
|
||||
const setSize = function(w, h) {
|
||||
const size = constrainSize(w, h, initialSize[0], initialSize[1]);
|
||||
player.style.width = size[0].toString() + 'px';
|
||||
player.style.height = size[1].toString() + 'px';
|
||||
};
|
||||
|
||||
const pub = {
|
||||
getElement: function() {
|
||||
return player;
|
||||
},
|
||||
appendTo: function(target) {
|
||||
target.appendChild(player);
|
||||
},
|
||||
insertBefore: function(ref) {
|
||||
$ib(ref, player);
|
||||
},
|
||||
nuke: function() {
|
||||
$r(player);
|
||||
},
|
||||
replaceElement(target) {
|
||||
$ib(target, player);
|
||||
$r(target);
|
||||
},
|
||||
getType: function() { return 'external'; },
|
||||
getWidth: function() { return width; },
|
||||
getHeight: function() { return height; },
|
||||
};
|
||||
|
||||
watchers.proxy(pub);
|
||||
|
||||
if(shouldObserveResize)
|
||||
player.addEventListener('resize', function() { setSize(player.videoWidth, player.videoHeight); });
|
||||
|
||||
player.addEventListener('play', function() { watchers.call('play', pub); });
|
||||
|
||||
const pPlay = function() { player.play(); };
|
||||
pub.play = pPlay;
|
||||
|
||||
const pPause = function() { player.pause(); };
|
||||
pub.pause = pPause;
|
||||
|
||||
let stopCalled = false;
|
||||
player.addEventListener('pause', function() {
|
||||
watchers.call(stopCalled ? 'stop' : 'pause', pub);
|
||||
stopCalled = false;
|
||||
});
|
||||
|
||||
const pStop = function() {
|
||||
stopCalled = true;
|
||||
player.pause();
|
||||
player.currentTime = 0;
|
||||
};
|
||||
pub.stop = pStop;
|
||||
|
||||
const pIsPlaying = function() { return !player.paused; };
|
||||
pub.isPlaying = pIsPlaying;
|
||||
|
||||
const pIsMuted = function() { return player.muted; };
|
||||
pub.isMuted = pIsMuted;
|
||||
|
||||
let lastMuteState = player.muted;
|
||||
player.addEventListener('volumechange', function() {
|
||||
if(lastMuteState !== player.muted) {
|
||||
lastMuteState = player.muted;
|
||||
watchers.call('mute', pub, [lastMuteState]);
|
||||
} else
|
||||
watchers.call('volume', pub, [player.volume]);
|
||||
});
|
||||
|
||||
const pSetMuted = function(state) { player.muted = state; };
|
||||
pub.setMuted = pSetMuted;
|
||||
|
||||
const pGetVolume = function() { return player.volume; };
|
||||
pub.getVolume = pGetVolume;
|
||||
|
||||
const pSetVolume = function(volume) { player.volume = volume; };
|
||||
pub.setVolume = pSetVolume;
|
||||
|
||||
const pGetPlaybackRate = function() { return player.playbackRate; };
|
||||
pub.getPlaybackRate = pGetPlaybackRate;
|
||||
|
||||
player.addEventListener('ratechange', function() {
|
||||
watchers.call('rate', pub, [player.playbackRate]);
|
||||
});
|
||||
|
||||
const pSetPlaybackRate = function(rate) { player.playbackRate = rate; };
|
||||
pub.setPlaybackRate = pSetPlaybackRate;
|
||||
|
||||
window.addEventListener('durationchange', function() {
|
||||
watchers.call('duration', pub, [player.duration]);
|
||||
});
|
||||
|
||||
const pGetDuration = function() { return player.duration; };
|
||||
pub.getDuration = pGetDuration;
|
||||
|
||||
window.addEventListener('timeupdate', function() {
|
||||
watchers.call('time', pub, [player.currentTime]);
|
||||
});
|
||||
|
||||
const pGetTime = function() { return player.currentTime; };
|
||||
pub.getTime = pGetTime;
|
||||
|
||||
const pSeek = function(time) { player.currentTime = time; };
|
||||
pub.seek = pSeek;
|
||||
|
||||
return pub;
|
||||
};
|
||||
|
||||
const MszVideoEmbedYouTube = function(metadata, options) {
|
||||
options = options || {};
|
||||
|
||||
const ytOrigin = 'https://www.youtube.com',
|
||||
playerId = 'yt-' + MszUniqueStr(8),
|
||||
shouldAutoplay = options.autoplay === undefined || options.autoplay;
|
||||
|
||||
let embedUrl = 'https://www.youtube.com/embed/' + metadata.youtube_video_id + '?enablejsapi=1';
|
||||
|
||||
embedUrl += '&origin=' + encodeURIComponent(location.origin);
|
||||
|
||||
if(metadata.youtube_start_time)
|
||||
embedUrl += '&t=' + encodeURIComponent(metadata.youtube_start_time);
|
||||
|
||||
if(metadata.youtube_playlist) {
|
||||
embedUrl += '&list=' + encodeURIComponent(metadata.youtube_playlist);
|
||||
|
||||
if(metadata.youtube_playlist_index)
|
||||
embedUrl += '&index=' + encodeURIComponent(metadata.youtube_playlist_index);
|
||||
}
|
||||
|
||||
let presetPlaybackRates = undefined,
|
||||
isMuted = undefined,
|
||||
volume = undefined,
|
||||
playbackRate = undefined,
|
||||
duration = undefined,
|
||||
currentTime = undefined,
|
||||
isPlaying = undefined;
|
||||
|
||||
const watchers = new MszWatcherCollection;
|
||||
watchers.define(MszVideoEmbedPlayerEvents());
|
||||
|
||||
const player = $e({
|
||||
tag: 'iframe',
|
||||
attrs: {
|
||||
frameborder: 0,
|
||||
allow: 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture',
|
||||
allowfullscreen: 'allowfullscreen',
|
||||
src: embedUrl,
|
||||
},
|
||||
});
|
||||
|
||||
const pub = {
|
||||
getElement: function() {
|
||||
return player;
|
||||
},
|
||||
appendTo: function(target) {
|
||||
target.appendChild(player);
|
||||
},
|
||||
insertBefore: function(ref) {
|
||||
$ib(ref, player);
|
||||
},
|
||||
nuke: function() {
|
||||
$r(player);
|
||||
},
|
||||
replaceElement(target) {
|
||||
$ib(target, player);
|
||||
$r(target);
|
||||
},
|
||||
getType: function() { return 'youtube'; },
|
||||
getWidth: function() { return 560; },
|
||||
getHeight: function() { return 315; },
|
||||
getPlayerId: function() { return playerId; },
|
||||
};
|
||||
|
||||
watchers.proxy(pub);
|
||||
|
||||
const postMessage = function(data) {
|
||||
player.contentWindow.postMessage(JSON.stringify(data), ytOrigin);
|
||||
};
|
||||
const postCommand = function(name, args) {
|
||||
postMessage({
|
||||
id: playerId,
|
||||
event: 'command',
|
||||
func: name,
|
||||
args: args || [],
|
||||
});
|
||||
};
|
||||
|
||||
const cmdPlay = function() { postCommand('playVideo'); };
|
||||
const cmdPause = function() { postCommand('pauseVideo'); };
|
||||
const cmdStop = function() { postCommand('stopVideo'); };
|
||||
const cmdMute = function() { postCommand('mute'); };
|
||||
const cmdUnMute = function() { postCommand('unMute'); };
|
||||
const cmdSetVolume = function(volume) { postCommand('setVolume', [volume * 100]); };
|
||||
const cmdSetPlaybackRate = function(rate) { postCommand('setPlaybackRate', [rate]); };
|
||||
const cmdSeekTo = function(timecode) { postCommand('seekTo', [timecode, true]); };
|
||||
|
||||
const cmdMuteState = function(state) {
|
||||
if(state) cmdMute();
|
||||
else cmdUnMute();
|
||||
};
|
||||
|
||||
const varIsPlaying = function() { return isPlaying; };
|
||||
const varIsMuted = function() { return isMuted; };
|
||||
const varVolume = function() { return volume; };
|
||||
const varRate = function() { return playbackRate; };
|
||||
const varDuration = function() { return duration; };
|
||||
const varTime = function() { return currentTime; };
|
||||
|
||||
let lastPlayerState = undefined,
|
||||
lastPlayerStateEvent = undefined;
|
||||
const handlePlayerState = function(state) {
|
||||
let eventName = undefined;
|
||||
if(state === 1) {
|
||||
isPlaying = true;
|
||||
eventName = 'play';
|
||||
} else {
|
||||
isPlaying = false;
|
||||
if(state === 2)
|
||||
eventName = 'pause';
|
||||
else if(lastPlayerState === -1 && state === 5)
|
||||
eventName = 'stop';
|
||||
}
|
||||
|
||||
lastPlayerState = state;
|
||||
if(eventName !== undefined && eventName !== lastPlayerStateEvent) {
|
||||
lastPlayerStateEvent = eventName;
|
||||
watchers.call(eventName, pub);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMuted = function(muted) {
|
||||
isMuted = muted;
|
||||
watchers.call('mute', pub, [isMuted]);
|
||||
};
|
||||
|
||||
const handleVolume = function(value) {
|
||||
volume = value / 100;
|
||||
watchers.call('volume', pub, [volume]);
|
||||
};
|
||||
|
||||
const handleRate = function(rate) {
|
||||
playbackRate = rate;
|
||||
watchers.call('rate', pub, [playbackRate]);
|
||||
};
|
||||
|
||||
const handleDuration = function(time) {
|
||||
duration = time;
|
||||
watchers.call('duration', pub, [duration]);
|
||||
};
|
||||
|
||||
const handleTime = function(time) {
|
||||
currentTime = time;
|
||||
watchers.call('time', pub, [currentTime]);
|
||||
};
|
||||
|
||||
const handlePresetRates = function(rates) {
|
||||
presetPlaybackRates = rates;
|
||||
};
|
||||
|
||||
const infoHandlers = {
|
||||
'playerState': handlePlayerState,
|
||||
'muted': handleMuted,
|
||||
'volume': handleVolume,
|
||||
'playbackRate': handleRate,
|
||||
'duration': handleDuration,
|
||||
'currentTime': handleTime,
|
||||
'availablePlaybackRates': handlePresetRates,
|
||||
};
|
||||
|
||||
const processInfo = function(info) {
|
||||
for(const name in infoHandlers)
|
||||
if(name in info && info[name] !== undefined)
|
||||
infoHandlers[name](info[name]);
|
||||
};
|
||||
|
||||
window.addEventListener('message', function(ev) {
|
||||
if(ev.origin !== ytOrigin || typeof ev.data !== 'string')
|
||||
return;
|
||||
|
||||
const data = JSON.parse(ev.data);
|
||||
if(!data || data.id !== playerId)
|
||||
return;
|
||||
|
||||
if(data.event === 'initialDelivery') {
|
||||
if(data.info !== undefined)
|
||||
processInfo(data.info);
|
||||
} else if(data.event === 'onReady') {
|
||||
if(shouldAutoplay)
|
||||
cmdPlay();
|
||||
} else if(data.event === 'infoDelivery') {
|
||||
if(data.info !== undefined)
|
||||
processInfo(data.info);
|
||||
}
|
||||
});
|
||||
|
||||
player.addEventListener('load', function(ev) {
|
||||
postMessage({
|
||||
id: playerId,
|
||||
event: 'listening',
|
||||
});
|
||||
});
|
||||
|
||||
pub.play = cmdPlay;
|
||||
pub.pause = cmdPause;
|
||||
pub.stop = cmdStop;
|
||||
pub.isPlaying = varIsPlaying;
|
||||
pub.isMuted = varIsMuted;
|
||||
pub.setMuted = cmdMuteState;
|
||||
pub.getVolume = varVolume;
|
||||
pub.setVolume = cmdSetVolume;
|
||||
pub.getRate = varRate;
|
||||
pub.setRate = cmdSetPlaybackRate;
|
||||
pub.getDuration = varDuration;
|
||||
pub.getTime = varTime;
|
||||
pub.seek = cmdSeekTo;
|
||||
|
||||
return pub;
|
||||
};
|
||||
|
||||
const MszVideoEmbedNicoNico = function(metadata, options) {
|
||||
options = options || {};
|
||||
|
||||
const nndOrigin = 'https://embed.nicovideo.jp',
|
||||
playerId = 'nnd-' + MszUniqueStr(8),
|
||||
shouldAutoplay = options.autoplay === undefined || options.autoplay;
|
||||
|
||||
let embedUrl = 'https://embed.nicovideo.jp/watch/' + metadata.nicovideo_video_id + '?jsapi=1&playerId=' + playerId;
|
||||
|
||||
if(metadata.nicovideo_start_time)
|
||||
embedUrl += '&from=' + encodeURIComponent(metadata.nicovideo_start_time);
|
||||
|
||||
let isMuted = undefined,
|
||||
volume = undefined,
|
||||
duration = undefined,
|
||||
currentTime = undefined,
|
||||
isPlaying = false;
|
||||
|
||||
const watchers = new MszWatcherCollection;
|
||||
watchers.define(MszVideoEmbedPlayerEvents());
|
||||
|
||||
const player = $e({
|
||||
tag: 'iframe',
|
||||
attrs: {
|
||||
frameborder: 0,
|
||||
allow: 'autoplay',
|
||||
allowfullscreen: 'allowfullscreen',
|
||||
src: embedUrl,
|
||||
},
|
||||
});
|
||||
|
||||
const pub = {
|
||||
getElement: function() {
|
||||
return player;
|
||||
},
|
||||
appendTo: function(target) {
|
||||
target.appendChild(player);
|
||||
},
|
||||
insertBefore: function(ref) {
|
||||
$ib(ref, player);
|
||||
},
|
||||
nuke: function() {
|
||||
$r(player);
|
||||
},
|
||||
replaceElement(target) {
|
||||
$ib(target, player);
|
||||
$r(target);
|
||||
},
|
||||
getType: function() { return 'nicovideo'; },
|
||||
getWidth: function() { return 640; },
|
||||
getHeight: function() { return 360; },
|
||||
getPlayerId: function() { return playerId; },
|
||||
};
|
||||
|
||||
watchers.proxy(pub);
|
||||
|
||||
const postMessage = function(name, data) {
|
||||
if(name === undefined)
|
||||
throw 'name must be specified';
|
||||
|
||||
player.contentWindow.postMessage({
|
||||
playerId: playerId,
|
||||
sourceConnectorType: 1,
|
||||
eventName: name,
|
||||
data: data,
|
||||
}, nndOrigin);
|
||||
};
|
||||
|
||||
const cmdPlay = function() { postMessage('play'); };
|
||||
const cmdPause = function() { postMessage('pause'); };
|
||||
const cmdMute = function(state) { postMessage('mute', { mute: state }); };
|
||||
const cmdSeek = function(time) { postMessage('seek', { time: time }); };
|
||||
const cmdVolumeChange = function(volume) { postMessage('volumeChange', { volume: volume }); };
|
||||
|
||||
let stopCalled = false;
|
||||
const cmdStop = function() {
|
||||
stopCalled = true;
|
||||
cmdPause();
|
||||
cmdSeek(0);
|
||||
};
|
||||
|
||||
const varIsPlaying = function() { return isPlaying; };
|
||||
const varIsMuted = function() { return isMuted; };
|
||||
const varVolume = function() { return volume; };
|
||||
const varDuration = function() { return duration; };
|
||||
const varTime = function() { return currentTime; };
|
||||
|
||||
let lastPlayerStateEvent = undefined;
|
||||
const handlePlayerStatus = function(status) {
|
||||
let eventName = undefined;
|
||||
if(status === 2) {
|
||||
isPlaying = true;
|
||||
eventName = 'play';
|
||||
} else {
|
||||
isPlaying = false;
|
||||
if(status === 4 || stopCalled) {
|
||||
eventName = 'stop';
|
||||
stopCalled = false;
|
||||
} else if(status === 3)
|
||||
eventName = 'pause';
|
||||
}
|
||||
|
||||
if(eventName !== undefined && eventName !== lastPlayerStateEvent) {
|
||||
lastPlayerStateEvent = eventName;
|
||||
watchers.call(eventName, pub);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMuted = function(muted) {
|
||||
isMuted = muted;
|
||||
watchers.call('mute', pub, [isMuted]);
|
||||
};
|
||||
|
||||
const handleVolume = function(value) {
|
||||
volume = value;
|
||||
watchers.call('volume', pub, [volume]);
|
||||
};
|
||||
|
||||
const handleDuration = function(time) {
|
||||
duration = time / 1000;
|
||||
watchers.call('duration', pub, [duration]);
|
||||
};
|
||||
|
||||
const handleTime = function(time) {
|
||||
currentTime = time / 1000;
|
||||
watchers.call('time', pub, [currentTime]);
|
||||
};
|
||||
|
||||
const metadataHanders = {
|
||||
'muted': handleMuted,
|
||||
'volume': handleVolume,
|
||||
'duration': handleDuration,
|
||||
'currentTime': handleTime,
|
||||
};
|
||||
const statusHandlers = {
|
||||
'playerStatus': handlePlayerStatus,
|
||||
};
|
||||
|
||||
const processData = function(handlers, info) {
|
||||
for(const name in handlers)
|
||||
if(name in info && info[name] !== undefined)
|
||||
handlers[name](info[name]);
|
||||
};
|
||||
|
||||
window.addEventListener('message', function(ev) {
|
||||
if(ev.origin !== nndOrigin || ev.data.playerId !== playerId)
|
||||
return;
|
||||
|
||||
if(ev.data.eventName === 'loadComplete') {
|
||||
if(shouldAutoplay)
|
||||
cmdPlay();
|
||||
} else if(ev.data.eventName === 'playerMetadataChange') {
|
||||
if(ev.data.data !== undefined)
|
||||
processData(metadataHanders, ev.data.data);
|
||||
} else if(ev.data.eventName === 'statusChange') {
|
||||
if(ev.data.data !== undefined)
|
||||
processData(statusHandlers, ev.data.data);
|
||||
}
|
||||
});
|
||||
|
||||
pub.play = cmdPlay;
|
||||
pub.pause = cmdPause;
|
||||
pub.stop = cmdStop;
|
||||
pub.isPlaying = varIsPlaying;
|
||||
pub.isMuted = varIsMuted;
|
||||
pub.setMuted = cmdMute;
|
||||
pub.getVolume = varVolume;
|
||||
pub.setVolume = cmdVolumeChange;
|
||||
pub.getDuration = varDuration;
|
||||
pub.getTime = varTime;
|
||||
pub.seek = cmdSeek;
|
||||
|
||||
return pub;
|
||||
};
|
||||
|
||||
const MszVideoEmbedPlaceholder = function(metadata, options) {
|
||||
options = options || {};
|
||||
|
||||
if(typeof options.player !== 'function' && typeof options.onclick !== 'function')
|
||||
throw 'Neither a player nor an onclick handler were provided.';
|
||||
|
||||
const shouldAutoSize = options.autosize === undefined || options.autosize;
|
||||
|
||||
const infoChildren = [];
|
||||
|
||||
infoChildren.push({
|
||||
tag: 'h1',
|
||||
attrs: {
|
||||
className: 'embedph-info-title',
|
||||
},
|
||||
child: metadata.title,
|
||||
});
|
||||
|
||||
if(metadata.description) {
|
||||
const firstLine = metadata.description.split("\n")[0].trim();
|
||||
if(firstLine.length > 300)
|
||||
firstLine = firstLine.substring(0, 300).trim() + '...';
|
||||
|
||||
infoChildren.push({
|
||||
tag: 'div',
|
||||
attrs: {
|
||||
className: 'embedph-info-desc',
|
||||
},
|
||||
child: firstLine,
|
||||
});
|
||||
}
|
||||
|
||||
infoChildren.push({
|
||||
tag: 'div',
|
||||
attrs: {
|
||||
className: 'embedph-info-site',
|
||||
},
|
||||
child: metadata.site_name,
|
||||
});
|
||||
|
||||
const style = [];
|
||||
if(typeof metadata.color !== 'undefined')
|
||||
style.push('--embedph-colour: ' + metadata.color);
|
||||
|
||||
if(!shouldAutoSize) {
|
||||
const size = MszVideoConstrainSize(
|
||||
options.width || metadata.width || 200,
|
||||
options.height || metadata.height || 200,
|
||||
options.maxWidth,
|
||||
options.maxHeight
|
||||
);
|
||||
|
||||
style.push('width: ' + size[0].toString() + 'px');
|
||||
style.push('height: ' + size[1].toString() + 'px');
|
||||
}
|
||||
|
||||
const pub = {};
|
||||
|
||||
const elem = $e({
|
||||
attrs: {
|
||||
href: 'javascript:void(0);',
|
||||
className: ('embedph embedph-' + (options.type || 'external')),
|
||||
style: style.join(';'),
|
||||
},
|
||||
child: [
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedph-bg',
|
||||
},
|
||||
child: {
|
||||
tag: 'img',
|
||||
attrs: {
|
||||
src: metadata.image,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedph-fg',
|
||||
},
|
||||
child: [
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedph-info',
|
||||
},
|
||||
child: {
|
||||
attrs: {
|
||||
className: 'embedph-info-wrap',
|
||||
},
|
||||
child: [
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedph-info-bar',
|
||||
},
|
||||
},
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedph-info-body',
|
||||
},
|
||||
child: infoChildren,
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedph-play',
|
||||
onclick: function(ev) {
|
||||
if(ev.target.tagName.toLowerCase() === 'a')
|
||||
return;
|
||||
|
||||
if(typeof options.onclick === 'function') {
|
||||
options.onclick(ev);
|
||||
return;
|
||||
}
|
||||
|
||||
const player = new options.player(metadata, options);
|
||||
let frameOrPlayer = player;
|
||||
|
||||
if(typeof options.frame === 'function')
|
||||
frameOrPlayer = new options.frame(player, options);
|
||||
|
||||
const embed = new MszVideoEmbed(frameOrPlayer);
|
||||
if(options.autoembed === undefined || options.autoembed)
|
||||
embed.replaceElement(elem);
|
||||
|
||||
if(typeof options.onembed === 'function')
|
||||
options.onembed(embed);
|
||||
},
|
||||
},
|
||||
child: [
|
||||
{
|
||||
attrs: {
|
||||
className: 'embedph-play-internal',
|
||||
},
|
||||
child: {
|
||||
tag: 'i',
|
||||
attrs: {
|
||||
className: 'fas fa-play fa-4x fa-fw',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'a',
|
||||
attrs: {
|
||||
className: 'embedph-play-external',
|
||||
href: metadata.url,
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
},
|
||||
child: ('or watch on ' + metadata.site_name + '?'),
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
pub.getElement = function() { return elem; };
|
||||
pub.appendTo = function(target) { target.appendChild(elem); };
|
||||
pub.insertBefore = function(ref) { $ib(ref, elem); };
|
||||
pub.nuke = function() {
|
||||
$r(elem);
|
||||
elem = null;
|
||||
};
|
||||
pub.replaceElement = function(target) {
|
||||
$ib(target, elem);
|
||||
$r(target);
|
||||
};
|
||||
|
||||
return pub;
|
||||
};
|
83
assets/js/misuzu/watcher.js
Normal file
83
assets/js/misuzu/watcher.js
Normal file
|
@ -0,0 +1,83 @@
|
|||
const MszWatcher = function() {
|
||||
let watchers = [];
|
||||
|
||||
return {
|
||||
watch: function(watcher, thisArg, args) {
|
||||
if(typeof watcher !== 'function')
|
||||
throw 'watcher must be a function';
|
||||
if(watchers.indexOf(watcher) >= 0)
|
||||
return;
|
||||
|
||||
watchers.push(watcher);
|
||||
|
||||
if(thisArg !== undefined) {
|
||||
if(!Array.isArray(args)) {
|
||||
if(args !== undefined)
|
||||
args = [args];
|
||||
else args = [];
|
||||
}
|
||||
|
||||
// initial call
|
||||
args.push(true);
|
||||
|
||||
watcher.apply(thisArg, args);
|
||||
}
|
||||
},
|
||||
unwatch: function(watcher) {
|
||||
$ari(watchers, watcher);
|
||||
},
|
||||
call: function(thisArg, args) {
|
||||
if(!Array.isArray(args)) {
|
||||
if(args !== undefined)
|
||||
args = [args];
|
||||
else args = [];
|
||||
}
|
||||
|
||||
args.push(false);
|
||||
|
||||
for(const watcher of watchers)
|
||||
watcher.apply(thisArg, args);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const MszWatcherCollection = function() {
|
||||
const collection = new Map;
|
||||
|
||||
const watch = function(name, watcher, thisArg, args) {
|
||||
const watchers = collection.get(name);
|
||||
if(watchers === undefined)
|
||||
throw 'undefined watcher name';
|
||||
watchers.watch(watcher, thisArg, args);
|
||||
};
|
||||
|
||||
const unwatch = function(name, watcher) {
|
||||
const watchers = collection.get(name);
|
||||
if(watchers === undefined)
|
||||
throw 'undefined watcher name';
|
||||
watchers.unwatch(watcher);
|
||||
};
|
||||
|
||||
return {
|
||||
define: function(names) {
|
||||
if(!Array.isArray(names))
|
||||
names = [names];
|
||||
for(const name of names)
|
||||
collection.set(name, new MszWatcher);
|
||||
},
|
||||
call: function(name, thisArg, args) {
|
||||
const watchers = collection.get(name);
|
||||
if(watchers === undefined)
|
||||
throw 'undefined watcher name';
|
||||
watchers.call(thisArg, args);
|
||||
},
|
||||
watch: watch,
|
||||
unwatch: unwatch,
|
||||
proxy: function(obj) {
|
||||
obj.watch = function(name, watcher) {
|
||||
watch(name, watcher);
|
||||
};
|
||||
obj.unwatch = unwatch;
|
||||
},
|
||||
};
|
||||
};
|
Loading…
Reference in a new issue