Add Niconico support
This commit is contained in:
parent
aeb5de85b6
commit
45217ccad8
|
@ -8,6 +8,7 @@ var order = [
|
||||||
'base.coffee',
|
'base.coffee',
|
||||||
|
|
||||||
'dailymotion.coffee',
|
'dailymotion.coffee',
|
||||||
|
'niconico.coffee',
|
||||||
'peertube.coffee',
|
'peertube.coffee',
|
||||||
'soundcloud.coffee',
|
'soundcloud.coffee',
|
||||||
'twitch.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
|
bc: IframeChild
|
||||||
bn: IframeChild
|
bn: IframeChild
|
||||||
od: OdyseePlayer
|
od: OdyseePlayer
|
||||||
|
nv: NicoPlayer
|
||||||
|
|
||||||
window.loadMediaPlayer = (data) ->
|
window.loadMediaPlayer = (data) ->
|
||||||
try
|
try
|
||||||
|
@ -109,7 +110,8 @@ window.removeOld = (replace) ->
|
||||||
$('#soundcloud-volume-holder').remove()
|
$('#soundcloud-volume-holder').remove()
|
||||||
replace ?= $('<div/>').addClass('embed-responsive-item')
|
replace ?= $('<div/>').addClass('embed-responsive-item')
|
||||||
old = $('#ytapiplayer')
|
old = $('#ytapiplayer')
|
||||||
|
old.attr('id', 'ytapiplayer-old')
|
||||||
|
replace.attr('id', 'ytapiplayer')
|
||||||
replace.insertBefore(old)
|
replace.insertBefore(old)
|
||||||
old.remove()
|
old.remove()
|
||||||
replace.attr('id', 'ytapiplayer')
|
|
||||||
return replace
|
return replace
|
||||||
|
|
|
@ -10,6 +10,7 @@ const Odysee = require("@cytube/mediaquery/lib/provider/odysee");
|
||||||
const PeerTube = require("@cytube/mediaquery/lib/provider/peertube");
|
const PeerTube = require("@cytube/mediaquery/lib/provider/peertube");
|
||||||
const BitChute = require("@cytube/mediaquery/lib/provider/bitchute");
|
const BitChute = require("@cytube/mediaquery/lib/provider/bitchute");
|
||||||
const BandCamp = require("@cytube/mediaquery/lib/provider/bandcamp");
|
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 Streamable = require("@cytube/mediaquery/lib/provider/streamable");
|
||||||
const TwitchVOD = require("@cytube/mediaquery/lib/provider/twitch-vod");
|
const TwitchVOD = require("@cytube/mediaquery/lib/provider/twitch-vod");
|
||||||
const TwitchClip = require("@cytube/mediaquery/lib/provider/twitch-clip");
|
const TwitchClip = require("@cytube/mediaquery/lib/provider/twitch-clip");
|
||||||
|
@ -416,6 +417,16 @@ var Getters = {
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
callback(error.message || 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":
|
case "od":
|
||||||
const [user,video] = id.split(';');
|
const [user,video] = id.split(';');
|
||||||
return `https://odysee.com/@${user}/${video}`;
|
return `https://odysee.com/@${user}/${video}`;
|
||||||
|
case "nv":
|
||||||
|
return `https://www.nicovideo.jp/watch/${id}`;
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -252,6 +252,7 @@ html(lang="en")
|
||||||
script(defer, src="https://www.youtube.com/iframe_api")
|
script(defer, src="https://www.youtube.com/iframe_api")
|
||||||
script(defer, src="https://api.dmcdn.net/all.js")
|
script(defer, src="https://api.dmcdn.net/all.js")
|
||||||
script(defer, src="https://player.vimeo.com/api/player.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/peertube.js")
|
||||||
script(defer, src="/js/sc.js")
|
script(defer, src="/js/sc.js")
|
||||||
script(defer, src="/js/video.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":
|
case "od":
|
||||||
const [user,video] = data.id.split(';');
|
const [user,video] = data.id.split(';');
|
||||||
return `https://odysee.com/@${user}/${video}`;
|
return `https://odysee.com/@${user}/${video}`;
|
||||||
|
case "nv":
|
||||||
|
return `https://www.nicovideo.jp/watch/${data.id}`;
|
||||||
default:
|
default:
|
||||||
return "#";
|
return "#";
|
||||||
}
|
}
|
||||||
|
@ -1406,6 +1408,11 @@ function parseMediaLink(url) {
|
||||||
return { type: 'bc', id: `${data.pathname.slice(7).split('/').shift()}` }
|
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':
|
case 'odysee.com':
|
||||||
const format = new RegExp('/@(?<user>[^:]+)(?::\\w)?/(?<video>[^:]+)');
|
const format = new RegExp('/@(?<user>[^:]+)(?::\\w)?/(?<video>[^:]+)');
|
||||||
if(format.test(data.pathname)){
|
if(format.test(data.pathname)){
|
||||||
|
|
Loading…
Reference in a new issue