Add Niconico support
This commit is contained in:
parent
aeb5de85b6
commit
45217ccad8
|
@ -8,6 +8,7 @@ var order = [
|
|||
'base.coffee',
|
||||
|
||||
'dailymotion.coffee',
|
||||
'niconico.coffee',
|
||||
'peertube.coffee',
|
||||
'soundcloud.coffee',
|
||||
'twitch.coffee',
|
||||
|
|
66
player/niconico.coffee
Normal file
66
player/niconico.coffee
Normal file
|
@ -0,0 +1,66 @@
|
|||
window.NicoPlayer = class NicoPlayer extends Player
|
||||
constructor: (data) ->
|
||||
if not (this instanceof NicoPlayer)
|
||||
return new NicoPlayer(data)
|
||||
|
||||
@load(data)
|
||||
|
||||
load: (data) ->
|
||||
@setMediaProperties(data)
|
||||
|
||||
waitUntilDefined(window, 'NicovideoEmbed', =>
|
||||
@nico = new NicovideoEmbed({ playerId: 'ytapiplayer', videoId: data.id })
|
||||
removeOld($(@nico.iframe))
|
||||
|
||||
@nico.on('ended', =>
|
||||
if CLIENT.leader
|
||||
socket.emit('playNext')
|
||||
)
|
||||
|
||||
@nico.on('pause', =>
|
||||
@paused = true
|
||||
if CLIENT.leader
|
||||
sendVideoUpdate()
|
||||
)
|
||||
|
||||
@nico.on('play', =>
|
||||
@paused = false
|
||||
if CLIENT.leader
|
||||
sendVideoUpdate()
|
||||
)
|
||||
|
||||
@nico.on('ready', =>
|
||||
@play()
|
||||
@setVolume(VOLUME)
|
||||
)
|
||||
)
|
||||
|
||||
play: ->
|
||||
@paused = false
|
||||
if @nico
|
||||
@nico.play()
|
||||
|
||||
pause: ->
|
||||
@paused = true
|
||||
if @nico
|
||||
@nico.pause()
|
||||
|
||||
seekTo: (time) ->
|
||||
if @nico
|
||||
@nico.seek(time * 1000)
|
||||
|
||||
setVolume: (volume) ->
|
||||
if @nico
|
||||
@nico.volumeChange(volume)
|
||||
|
||||
getTime: (cb) ->
|
||||
if @nico
|
||||
cb(parseFloat(@nico.state.currentTime / 1000))
|
||||
else
|
||||
cb(0)
|
||||
|
||||
getVolume: (cb) ->
|
||||
if @nico
|
||||
cb(parseFloat(@nico.state.volume))
|
||||
else
|
||||
cb(VOLUME)
|
|
@ -18,6 +18,7 @@ TYPE_MAP =
|
|||
bc: IframeChild
|
||||
bn: IframeChild
|
||||
od: OdyseePlayer
|
||||
nv: NicoPlayer
|
||||
|
||||
window.loadMediaPlayer = (data) ->
|
||||
try
|
||||
|
@ -109,7 +110,8 @@ window.removeOld = (replace) ->
|
|||
$('#soundcloud-volume-holder').remove()
|
||||
replace ?= $('<div/>').addClass('embed-responsive-item')
|
||||
old = $('#ytapiplayer')
|
||||
old.attr('id', 'ytapiplayer-old')
|
||||
replace.attr('id', 'ytapiplayer')
|
||||
replace.insertBefore(old)
|
||||
old.remove()
|
||||
replace.attr('id', 'ytapiplayer')
|
||||
return replace
|
||||
|
|
|
@ -10,6 +10,7 @@ const Odysee = require("@cytube/mediaquery/lib/provider/odysee");
|
|||
const PeerTube = require("@cytube/mediaquery/lib/provider/peertube");
|
||||
const BitChute = require("@cytube/mediaquery/lib/provider/bitchute");
|
||||
const BandCamp = require("@cytube/mediaquery/lib/provider/bandcamp");
|
||||
const Nicovideo = require("@cytube/mediaquery/lib/provider/nicovideo");
|
||||
const Streamable = require("@cytube/mediaquery/lib/provider/streamable");
|
||||
const TwitchVOD = require("@cytube/mediaquery/lib/provider/twitch-vod");
|
||||
const TwitchClip = require("@cytube/mediaquery/lib/provider/twitch-clip");
|
||||
|
@ -416,6 +417,16 @@ var Getters = {
|
|||
}).catch(error => {
|
||||
callback(error.message || error);
|
||||
});
|
||||
},
|
||||
|
||||
/* Niconico */
|
||||
nv: function (id, callback) {
|
||||
Nicovideo.lookup(id).then(video => {
|
||||
video = new Media(video.id, video.title, video.duration, "nv", video.meta);
|
||||
callback(null, video);
|
||||
}).catch(error => {
|
||||
callback(error.message || error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -216,6 +216,8 @@
|
|||
case "od":
|
||||
const [user,video] = id.split(';');
|
||||
return `https://odysee.com/@${user}/${video}`;
|
||||
case "nv":
|
||||
return `https://www.nicovideo.jp/watch/${id}`;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -252,6 +252,7 @@ html(lang="en")
|
|||
script(defer, src="https://www.youtube.com/iframe_api")
|
||||
script(defer, src="https://api.dmcdn.net/all.js")
|
||||
script(defer, src="https://player.vimeo.com/api/player.js")
|
||||
script(defer, src="/js/niconico.js")
|
||||
script(defer, src="/js/peertube.js")
|
||||
script(defer, src="/js/sc.js")
|
||||
script(defer, src="/js/video.js")
|
||||
|
|
234
www/js/niconico.js
Normal file
234
www/js/niconico.js
Normal file
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* Niconico iframe embed api
|
||||
* Written by Xaekai
|
||||
* Copyright (c) 2022 Radiant Feather; Licensed AGPLv3
|
||||
*
|
||||
*/
|
||||
class NicovideoEmbed {
|
||||
static origin = 'https://embed.nicovideo.jp';
|
||||
static methods = [
|
||||
'loadComplete',
|
||||
'mute',
|
||||
'pause',
|
||||
'play',
|
||||
'seek',
|
||||
'volumeChange',
|
||||
];
|
||||
static frames = [
|
||||
'error',
|
||||
'loadComplete',
|
||||
'playerMetadataChange',
|
||||
'playerStatusChange',
|
||||
'seekStatusChange',
|
||||
'statusChange',
|
||||
//'player-error:video:play',
|
||||
//'player-error:video:seek',
|
||||
];
|
||||
static events = [
|
||||
'ended',
|
||||
'error',
|
||||
'muted',
|
||||
'pause',
|
||||
'play',
|
||||
'progress',
|
||||
'ready',
|
||||
'timeupdate',
|
||||
'unmuted',
|
||||
'volumechange',
|
||||
];
|
||||
|
||||
constructor(options) {
|
||||
this.handlers = Object.fromEntries(NicovideoEmbed.frames.map(key => [key,[]]));
|
||||
this.listeners = Object.fromEntries(NicovideoEmbed.events.map(key => [key,[]]));
|
||||
this.state = ({
|
||||
ready: false,
|
||||
playerStatus: 1,
|
||||
currentTime: 0.0, // ms
|
||||
muted: false,
|
||||
volume: 0.99,
|
||||
maximumBuffered: 0,
|
||||
});
|
||||
|
||||
this.setupHandlers();
|
||||
this.scaffold(options);
|
||||
}
|
||||
|
||||
scaffold({ iframe = null, playerId = 1, videoId = null }){
|
||||
this.playerId = playerId;
|
||||
this.messageListener();
|
||||
if(iframe === null){
|
||||
if(videoId === null){
|
||||
throw new Error('You must provide either an existing iframe or a videoId');
|
||||
}
|
||||
const iframe = this.iframe = document.createElement('iframe');
|
||||
|
||||
const source = new URL(`${NicovideoEmbed.origin}/watch/${videoId}`);
|
||||
source.search = new URLSearchParams({
|
||||
jsapi: 1,
|
||||
playerId
|
||||
});
|
||||
iframe.setAttribute('src', source);
|
||||
iframe.setAttribute('id', playerId);
|
||||
iframe.setAttribute('allow', 'autoplay; fullscreen');
|
||||
iframe.addEventListener('load', ()=>{
|
||||
this.observe();
|
||||
})
|
||||
} else {
|
||||
this.iframe = iframe;
|
||||
this.observe();
|
||||
}
|
||||
}
|
||||
|
||||
setupHandlers() {
|
||||
this.handlers.loadComplete.push((data) => {
|
||||
this.emit('ready');
|
||||
this.state.ready = true;
|
||||
Object.assign(this, data);
|
||||
});
|
||||
this.handlers.error.push((data) => {
|
||||
this.emit('error', data);
|
||||
});
|
||||
this.handlers.playerStatusChange.push((data) => {
|
||||
let event;
|
||||
switch (data.playerStatus) {
|
||||
case 1: /* Buffering */ return;
|
||||
case 2: event = 'play'; break;
|
||||
case 3: event = 'pause'; break;
|
||||
case 4: event = 'ended'; break;
|
||||
}
|
||||
this.state.playerStatus = data.playerStatus;
|
||||
this.emit(event);
|
||||
});
|
||||
this.handlers.playerMetadataChange.push(({ currentTime, volume, muted, maximumBuffered }) => {
|
||||
const self = this.state;
|
||||
|
||||
if (currentTime !== self.currentTime) {
|
||||
self.currentTime = currentTime;
|
||||
this.emit('timeupdate', currentTime);
|
||||
}
|
||||
|
||||
if (muted !== self.muted) {
|
||||
self.muted = muted;
|
||||
this.emit(muted ? 'muted' : 'unmuted');
|
||||
}
|
||||
|
||||
if (volume !== self.volume) {
|
||||
self.volume = volume;
|
||||
this.emit('volumechange', volume);
|
||||
}
|
||||
|
||||
if (maximumBuffered !== self.maximumBuffered) {
|
||||
self.maximumBuffered = maximumBuffered;
|
||||
this.emit('progress', maximumBuffered);
|
||||
}
|
||||
});
|
||||
this.handlers.seekStatusChange.push((data) => {
|
||||
//
|
||||
});
|
||||
this.handlers.statusChange.push((data) => {
|
||||
//
|
||||
});
|
||||
}
|
||||
|
||||
messageListener() {
|
||||
const dispatcher = (event) => {
|
||||
if (event.origin === NicovideoEmbed.origin && event.data.playerId === this.playerId) {
|
||||
const { data } = event.data;
|
||||
this.dispatch(event.data.eventName, data);
|
||||
}
|
||||
}
|
||||
window.addEventListener('message', dispatcher);
|
||||
|
||||
/* Clean up */
|
||||
this.observer = new MutationObserver((alterations) => {
|
||||
alterations.forEach((change) => {
|
||||
change.removedNodes.forEach((deletion) => {
|
||||
if(deletion.nodeName === 'IFRAME') {
|
||||
window.removeEventListener('message', dispatcher)
|
||||
this.observer.disconnect();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
observe(){
|
||||
this.state.receptive = true;
|
||||
this.observer.observe(this.iframe.parentElement, { subtree: true, childList: true });
|
||||
}
|
||||
|
||||
dispatch(frame, data = null){
|
||||
if(!NicovideoEmbed.frames.includes(frame)){
|
||||
console.error(JSON.stringify(data, undefined, 4));
|
||||
throw new Error(`NicovideoEmbed ${frame}`);
|
||||
}
|
||||
[...this.handlers[frame]].forEach(handler => {
|
||||
handler.call(this, data);
|
||||
});
|
||||
}
|
||||
|
||||
emit(event, data = null){
|
||||
[...this.listeners[event]].forEach(listener => {
|
||||
listener.call(this, data);
|
||||
});
|
||||
if(event === 'ready'){
|
||||
this.listeners.ready.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
postMessage(request) {
|
||||
if(!this.state.receptive){
|
||||
setTimeout(() => { this.postMessage(request) }, 1000 / 24);
|
||||
return;
|
||||
}
|
||||
const message = Object.assign({
|
||||
sourceConnectorType: 1,
|
||||
playerId: this.playerId
|
||||
}, request);
|
||||
|
||||
this.iframe.contentWindow.postMessage(message, NicovideoEmbed.origin);
|
||||
}
|
||||
|
||||
on(event, listener){
|
||||
if(!NicovideoEmbed.events.includes(event)){
|
||||
throw new Error('Unrecognized event name');
|
||||
}
|
||||
if(event === 'ready'){
|
||||
if(this.state.ready){
|
||||
listener();
|
||||
return this;
|
||||
} else {
|
||||
setTimeout(() => { this.loadComplete() }, 1000 / 60);
|
||||
}
|
||||
}
|
||||
this.listeners[event].push(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
mute(state){
|
||||
this.postMessage({ eventName: 'pause', data: { mute: state } });
|
||||
}
|
||||
|
||||
pause(){
|
||||
this.postMessage({ eventName: 'pause' });
|
||||
}
|
||||
|
||||
play(){
|
||||
this.postMessage({ eventName: 'play' });
|
||||
}
|
||||
|
||||
loadComplete(){
|
||||
this.postMessage({ eventName: 'loadComplete' });
|
||||
}
|
||||
|
||||
seek(ms){
|
||||
this.postMessage({ eventName: 'seek', data: { time: ms } });
|
||||
}
|
||||
|
||||
volumeChange(volume){
|
||||
this.postMessage({ eventName: 'pause', data: { volume } });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
window.NicovideoEmbed = NicovideoEmbed;
|
|
@ -67,6 +67,8 @@ function formatURL(data) {
|
|||
case "od":
|
||||
const [user,video] = data.id.split(';');
|
||||
return `https://odysee.com/@${user}/${video}`;
|
||||
case "nv":
|
||||
return `https://www.nicovideo.jp/watch/${data.id}`;
|
||||
default:
|
||||
return "#";
|
||||
}
|
||||
|
@ -1406,6 +1408,11 @@ function parseMediaLink(url) {
|
|||
return { type: 'bc', id: `${data.pathname.slice(7).split('/').shift()}` }
|
||||
}
|
||||
|
||||
case 'nicovideo.jp':
|
||||
if(data.pathname.startsWith('/watch/')){
|
||||
return { type: 'nv', id: `${data.pathname.slice(7).split('/').shift()}` }
|
||||
}
|
||||
|
||||
case 'odysee.com':
|
||||
const format = new RegExp('/@(?<user>[^:]+)(?::\\w)?/(?<video>[^:]+)');
|
||||
if(format.test(data.pathname)){
|
||||
|
|
Loading…
Reference in a new issue