|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="utf-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|
<script>if (document.URL !== location.href) location.href = location.href</script> |
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/flowplayer/7.2.6/skin/skin.css" rel="stylesheet"> |
|
<style type="text/css"> |
|
body { |
|
font-family: sans-serif; |
|
} |
|
/* @font-face { |
|
font-family: 'Hiragino Sans W6'; |
|
font-weight: 600; |
|
src: local('Hiragino Sans W6'), local('Hiragino Kaku Gothic ProN W6'); |
|
} |
|
@font-face { |
|
font-family: 'Hiragino Sans W3'; |
|
font-weight: 300; |
|
src: local('Hiragino Sans W3'), local('Hiragino Kaku Gothic ProN W3'); |
|
} |
|
@font-face { |
|
font-family: 'MS PGothic AA'; |
|
font-weight: 600; |
|
src: url('/pubshare/MS-PGothic-AA.ttf'); */ |
|
} |
|
.ASS-dialogue > span { |
|
font-size-adjust: 0.48; |
|
-moz-osx-font-smoothing: grayscale; |
|
} |
|
</style> |
|
|
|
</head> |
|
<body> |
|
<p><select id="episodes"></select></p> |
|
<p><a href=".">Index</a> <a id="switch" href="player-wasm.html">Switch to native renderer (WebAssembly)</a></p> |
|
|
|
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.10.4/polyfill.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/flowplayer/7.2.6/flowplayer.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/gh/CL-Jeremy/ASS@next/dist/ass.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/fetch-polyfill2/dist/index.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/gh/mhertsch/gruft/src/gruft-common.js"></script> |
|
<script src="https://cdn.jsdelivr.net/gh/mhertsch/gruft/src/gruft-tiger192.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/css-element-queries/src/ResizeSensor.js"></script> |
|
|
|
<script type="text/babel"> |
|
$(() => { |
|
var newPlayer = () => $('<div id="player" class="fp-slim"></div>').appendTo('body'); |
|
var params, api, player; |
|
|
|
var getQueryStringParams = query => query |
|
? (/^[?#]/.test(query) ? query.slice(1) : query) |
|
.split('&') |
|
.reduce((params, param) => { |
|
let [key, value] = param.split('='); |
|
params[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : ''; |
|
return params; |
|
}, {}) |
|
: {}; |
|
|
|
var setQueryString = (href, newQueryString) => href.replace(/([^?#]*)(?:\?[^?#]*)?(#[^?#]*)?/, `$1\?${newQueryString}$2`); |
|
|
|
var getQueryString = href => href.replace(/[^?#]*(?:\?([^?#]*))?(?:#[^?#]*)?/, '$1'); |
|
|
|
var editQueryString = (href, key, value) => { |
|
params = getQueryStringParams(getQueryString(href)); |
|
return key in params |
|
? href.replace(`${key}=${encodeURIComponent(params[key])}`, value === null |
|
? '' |
|
: `${key}=${value}`).replace(/[?&]$/, '') |
|
: value === null |
|
? href |
|
: href.replace(/([^?#]*)(\?[^?#]*)?(#[^?#]*)?/, `$1${getQueryString(href) ? '$2&' : '?'}${key}=${value}$3`); |
|
}; |
|
|
|
var getParams = () => getQueryStringParams(getQueryString(window.location.href)); |
|
|
|
var setParam = (key, value) => { |
|
if (encodeURIComponent(getParams()[key]) != value) |
|
window.history.pushState({}, null, editQueryString(window.location.href, key, value)); |
|
}; |
|
|
|
const selector = $('#episodes'); |
|
selector.on('input', () => { |
|
$('p:has(a)').html((index, html) => html.replace(/ <a id="bookmark".*\/a>/, '')); |
|
setParam('s', null); |
|
api.shutdown(); |
|
$('#player').remove(); |
|
playCurrentEpisode(); |
|
}); |
|
|
|
const tiger = new gruft.Tiger192(); |
|
|
|
var getVid = text => tiger.digest(text, { format: 'base64_safe' }).slice(0, 8); |
|
|
|
var findEpisodeByVid = vid => $('#episodes option').filter((index, node) => getVid($(node).text()) === vid); |
|
|
|
var findEpisode = name => $('#episodes option').filter((index, node) => $(node).text() === name); |
|
|
|
var countEpisodes = () => $('#episodes option').length; |
|
|
|
var hasEpisode = name => findEpisode(name).length ? true : false; |
|
|
|
var getEpisode = name => findEpisode(name).prop('value'); |
|
|
|
var getEpisodeAtIndex = index => $(`#episodes :nth-child(${index})`).text(); |
|
|
|
var onReadyHandler = () => { |
|
if ('s' in getParams()) |
|
api.seek(getParams()['s']); |
|
var video = $('.fp-engine'); |
|
video.on('timeupdate', () => { |
|
$('#switch, #bookmark').prop('href', (index, href) => |
|
editQueryString(href, 's', api.video.time == 0 ? null : ~~api.video.time)); |
|
}); |
|
fetch(video.attr('src').replace(/\.[^.]*$/i, '_comments.ass'), { credentials: 'include' }) |
|
.then(res => res.text()) |
|
.then((text) => { |
|
const ass = new ASS(text, video[0], { |
|
container: $('.fp-player')[0], |
|
resampling: 'video-width' |
|
}); |
|
new ResizeSensor(player, () => { |
|
$('.fp-player').attr('style', `width: ${player.css('width')}; height: ${player.css('height')};`); |
|
ass.resize(); |
|
}); |
|
ass.show(); |
|
$('.fp-player').attr('tabIndex', -1); |
|
$('.fp-player').on('keydown', e => { |
|
if (e.shiftKey) { |
|
if (e.keyCode === 38) $('.ASS-stage').css('opacity', '+=0.1'); |
|
if (e.keyCode === 40) $('.ASS-stage').css('opacity', '-=0.1'); |
|
} |
|
}); |
|
}); |
|
}; |
|
|
|
var playCurrentEpisode = () => { |
|
var file = selector.val(); |
|
setParam('id', null); |
|
setParam('v', null); |
|
setParam('vid', getVid(getEpisodeAtIndex(selector.prop('selectedIndex') + 1))); |
|
player = newPlayer(); |
|
api = flowplayer('#player', { |
|
seekStep: 5, |
|
clip: { |
|
sources: [ |
|
{ type: "video/" + file.match(/\.[^.]+$/)[0].slice(1), src: file.replace(/([,?:@&=+$#])/, encodeURIComponent) } |
|
] |
|
} |
|
}).on('ready', onReadyHandler); |
|
if ('s' in getParams()) |
|
api.video.time = getParams()['s']; |
|
$('p:has(a)').append(` <a id="bookmark" href=${location.pathname.split("/").slice(-1)}>Bookmark current position</a>`); |
|
$('#switch, #bookmark').prop('href', (index, href) => setQueryString(href, getQueryString(window.location.href))); |
|
}; |
|
|
|
var populate = dir => fetch(dir, { credentials: 'include' }) |
|
.then(res => res.text()) |
|
.then((text) => { |
|
$($.parseHTML(text)) |
|
.filter('table') |
|
.find('a') |
|
.each((i, el) => { |
|
var file = $.trim(el.innerText); |
|
if (file.match(/\.(mp4|mkv|avi|webm)+$/)) { |
|
$('#episodes').append($('<option>', { |
|
value: dir + file, |
|
text: file.replace(/\.[^.]*$/i, '') |
|
})); |
|
} |
|
}); |
|
}); |
|
|
|
Promise.all(['./', 'enjou/'].map(populate)) |
|
.then(() => selector.html(selector.find('option').sort((a,b) => { |
|
return (a.innerHTML > b.innerHTML) ? 1 : -1 |
|
}))) |
|
.then(() => { |
|
if ('vid' in getParams()) { |
|
const v = findEpisodeByVid(getParams()['vid']); |
|
if (v.length) { |
|
selector.val(v.prop('value')); |
|
return; |
|
} |
|
} |
|
if ('v' in getParams() && hasEpisode(decodeURIComponent(getParams()['v']))) |
|
selector.val(getEpisode(decodeURIComponent(getParams()['v']))); |
|
else if ('id' in getParams()) |
|
selector.prop('selectedIndex', getParams()['id'] == 0 |
|
? 0 |
|
: +getParams()['id'] + ~~(1 - (getParams()['id']) / countEpisodes()) * countEpisodes() - 1); |
|
}) |
|
.then(playCurrentEpisode); |
|
}); |
|
</script> |
|
</body> |
|
</html> |