Skip to content

Instantly share code, notes, and snippets.

@syoichi
Created October 15, 2015 10:51
Show Gist options
  • Save syoichi/ffa7a5901d9c73ef96d5 to your computer and use it in GitHub Desktop.
Save syoichi/ffa7a5901d9c73ef96d5 to your computer and use it in GitHub Desktop.
ニコ動をHTML5で再生するスクリプト のチャンネル動画対応版を勝手に作った | FNB https://fushihara.org/blog/archives/3132 のソースコード
// ==UserScript==
// @name kusa5
// @namespace net.buhoho.kusa5
// @include http://www.nicovideo.jp/watch/*
// @version 1
// @grant none
// @description ニコ動html5表示
// ==/UserScript==
//localStorage.kusa5buffer : trueにするとバッファリングするようになる
//localStorage.kusa5disableAutoplay : trueにすると自動再生しない
//開発ツールのコンソールにて
//localStorage.kusa5buffer = true;
//みたいに打ち込んで設定する
'use strict';
$('.playerContainer').hide();
//$('#playlist').hide(); //お好み
$('#playerContainerSlideArea').attr('id', 'kusa5');
$('#playerContainerWrapper').insertBefore('.videoHeaderOuter'); // お好み
$(".notify_update_flash_player").hide(); //Flash未インストール警告
const OPT = {
buffer: localStorage.kusa5buffer, // たぶんfirefoxじゃないと正常に動かない
debug: false
};
const ASKURL = 'http://flapi.nicovideo.jp/api/getflv?v=';
const THUMB = 'http://tn-skr3.smilevideo.jp/smile?i=';
const WATCH = 'http://www.nicovideo.jp/watch/';
const apidata = JSON.parse($('#watchAPIDataContainer').text());
const launchID = apidata.videoDetail.v;
const isIframe = window != parent;
const ICON = "https://cdnjs.cloudflare.com/ajax/libs/foundicons/3.0.0/svgs/";
addGlobalStyle(`
#kusa5 {
position: relative;
/*background-color: hsla(180, 10%, 0%, 0.8);*/
background-color: #000;
width: 640px;
height: 360px;
overflow: hidden;
margin: 0 auto;
}
#kusa5 video {
display: block;
background-color: #000;
height: 720px;
max-width: 100%; /* 画面外にはみ出ないように */
margin: 0 auto;
}
#wallImageContainer .wallImageCenteringArea .wallAlignmentArea.image2{
z-index: 3;
background-color: #CCCEC3;
/*
background-image: url('${ THUMB + launchID }');
filter: contrast(0.25) opacity(.25);
-webkit-filter: contrast(0.25) opacity(.25);
*/
}
#playerContainerWrapper {
padding: 60px 0;
}
/*
コントロールパネル関係
******************************************************************************/
.controle-panel {
z-index: 10; /* コメントより手前 */
color: rgba(255, 255, 255, 0.87);
text-shadow: 2px 1px #000;
position:absolute;
bottom: 0;
width: 100%;
background: #0C2529;
transition: transform .2s;
/* transform: translate3d(0, 47px, 0); */
overflow: hidden;
cursor: default;
height: 50px;
}
#kusa5:hover .controle-panel {
transform: translate3d(0, 0, 0);
}
.controle-panel .btn,
input+label {
color: rgba(255, 255, 255, 0.87);
font-size: 18px;
border: none;
background-color: transparent;
}
.controle-panel .r {float: right;}
.controle-panel .progressBar {
cursor: pointer;
position: relative;
height: 14px;
background-color: rgba(255, 255, 255, 0.24);
width: 100%;
}
.controle-panel .progressBar span {
position: absolute;;
top: 0;
left: 0;
width: 0;
height: 100%;
}
.controle-panel .progressBar.seek .mainbar {
background-color: #8CD6E7;
}
.controle-panel .progressBar.seek .bufferbar {
background-color: rgba(168, 191, 210, 0.37);
}
.controle-panel .progressBar.buf { height: 2px;}
.controle-panel .progressBar.buf .bar { background-color: #D736A2}
button.btn.ico {
color: #000;
position: relative;
background-repeat: no-repeat;
background-size: contain;
filter: invert(1) opacity(0.8);
-webkit-filter: invert(1) opacity(0.8);
width: 25px;
height: 25px;
background-size: 18px;
background-position: 5px;
margin: 4px 5px;
cursor: pointer;
}
button.btn.ico:hover {
filter: invert(1) opacity(1);
-webkit-filter: invert(1) opacity(1);
}
button.btn.ico.play {
background-image: url("${ICON}fi-play.svg");
}
#kusa5.playing button.btn.ico.play {
/* 再生中 */
background-image: url("${ICON}fi-pause.svg");
}
button.btn.ico:active {
transform: scale(0.9);
}
button.btn.ico.volumedown {
background-image: url("${ICON}fi-volume-none.svg");
}
button.btn.ico.volumeup {
background-image: url("${ICON}fi-volume.svg");
}
button.btn.volume > span.volume-num {
font-size: 10px;
position: absolute;
top: 0;
right: -8px;
}
button.btn.ico.full {
background-image: url("${ICON}fi-arrows-expand.svg");
}
#kusa5:-moz-full-screen button.btn.ico.full {
background-image: url("${ICON}fi-arrows-compress.svg");
}
#kusa5:-webkit-full-screen button.btn.ico.full {
background-image: url("${ICON}fi-arrows-compress.svg");
}
.controle-panel .playtime {
line-height: 32px;
}
button.btn.ico.comment-hidden {
background-image: url(https://cdnjs.cloudflare.com/ajax/libs/topcoat-icons/0.1.0/svg/chat.svg);
}
/* 非表示状態 */
#kusa5.comment-hidden .msg { opacity: 0;}
button.comment-hidden {
opacity: 1;
}
#kusa5.comment-hidden button.comment-hidden {
opacity: 0.5;
}
input.btn {
display: none;
}
input.btn+label {
color: #999;
display: inline-block;
background-color: hsla(0. 0%, 0%, 0.3);
text-align: center;
}
input.btn+label:hover,
input.btn:checked+label {
color: #fff;
text-decoration-line: underline;
}
input.btn+label span{
font-size:0.5em;
}
div.ratepanel {
display: inline-block;
text-align: center;
}
/*
コメント要素関連
******************************************************************************/
#kusa5 .msg {
z-index: 5;
display: inline-block;
word-break: keep-all;
font-size: 1.8em;
color: white;
padding: 0 .5em;
position: absolute;
transition-duration: 6s;
transition-timing-function: linear;
transition-property: transform;
transform: translate3d(105% ,0,0); /* 画面外に配置するので */
text-shadow:
0px 0.05em 1px #222,
0.05em 0px 1px #222,
0px -0.05em 1px #222,
-0.05em 0px 1px #444;
top: 0;
}
#kusa5 .msg.l1 { top: calc(1.4em * 0);}
#kusa5 .msg.l2 { top: calc(1.4em * 1);}
#kusa5 .msg.l3 { top: calc(1.4em * 2);}
#kusa5 .msg.l4 { top: calc(1.4em * 3);}
#kusa5 .msg.l5 { top: calc(1.4em * 4);}
#kusa5 .msg.l6 { top: calc(1.4em * 5);}
#kusa5 .msg.l7 { top: calc(1.4em * 6);}
#kusa5 .msg.l8 { top: calc(1.4em * 7);}
#kusa5 .msg.l9 { top: calc(1.4em * 8);}
#kusa5 .msg.l10 { top: calc(1.4em * 9);}
#kusa5 .msg.l11 { top: calc(1.4em * 10);}
#kusa5 .msg.l12 { top: calc(1.4em * 11);}
#kusa5 .msg.l13 { top: calc(1.4em * 12);}
/*
フルスクリーン関連
******************************************************************************/
/* 何故か一つづつfont-size指定しないと効かない */
#kusa5:-moz-full-screen .msg {font-size: 3.5em; }
#kusa5:-webkit-full-screen .msg {font-size: 3.5em; }
#kusa5:-webkit-full-screen {
width: 100%;
height: 100%;
}
/*
左上に縮小表示中
******************************************************************************/
body.size_small.no_setting_panel.videoExplorer #kusa5 {
height: 100%;
width: 100%;
margin: 0;
}
body.size_small.no_setting_panel.videoExplorer #kusa5 .msg{
font-size: 12px;
}
@media (max-width: 1399px) and (min-width: 1140px) {
#kusa5 { width: 854px; height: 480px; }
#kusa5 .msg{ font-size: 2.0em; }
}
/** 大画面向け */
@media (min-width: 1400px) {
#kusa5 { width: 1280px; height: 770px; }
#kusa5 .msg{ font-size: 2.8em; }
}
`);
const $video = $(`<video type="video/mp4"'
codecs="avc1.42E01E, mp4a.40.2"
autoplay />`)
.on('ended', buffShift)
.on('pause', ev => {
$("#kusa5").removeClass("playing");
localStorage.nicoRate = ev.target.playbackRate;
})
.on('play', ev => {
$("#kusa5").addClass("playing");
// レート情報の記憶
$('input[value="'+ localStorage.nicoRate +'"]').click();
ev.target.playbackRate = localStorage.nicoRate;
ev.target.volume = localStorage.nicoVolume * 0.2;
if (!isIframe)
return;
// バッファー再生用のプレーヤーは処理を重くしないためにrata1
ev.target.playbackRate = 1;
$(ev.target).off().prop('muted', true);
});
if (localStorage.kusa5disableAutoplay === "true")
$video.removeAttr('autoplay');
$video.videoToggle = function() {
var v = $video[0];
v.paused ? v.play() : v.pause();
};
$video.click($video.videoToggle);
function addGlobalStyle(css) {
var styleSeet = $('<style type="text/css">');
styleSeet.text(css);
$('head').append(styleSeet);
}
/* 現在のページのDOMから次動画のIDを取得する。
* なので次の次の動画のIDを取るなら事前にヘッダーを書き換えておく必要がある。
*/
function getNextId() {
const currentID = $('#kusa5 video').data('smid');
const id = /\W?(s[mo]\d{3,10})\W?/.source; // 現状見受けられる動画は8桁
const next = "(?:" + ["次","next","つづ","続","最","終"]
.map(s => s + ".{0,4}")
.join("|") + ")";
//const prev = ["前","prev","まえ","ぜん","一","初"];
// スペースに使われそうな文字(出現しないかもしれない)
const s = /[\s_|::]?/.source;
const arrows = [
" - ",
"←",
"→","⇒",
":", ":",
"<","<<","<<","≪","«",
">",">>",">>","≫","»",
"[<<≪«][-ー==]", // 二文字組み合わせやじるし
"[-ー==][>>≫»]"];
const _A_ = s + "(?:" + arrows.join("|") + ")" + s;
const next_id = next + _A_ + id ;
const id_next = id + _A_ + next;
//const prev_id = s + prev + _A_ + id + s;
//const id_prev = s + id + _A_ + prev + s;
const 主米 = $('.description').text();
var m = _.reduce([next_id, id_next], (c,re) => {
return c || 主米.match(new RegExp(re, 'i'));
},false);
OPT.debug && alert(!!m && !!m[0] ?
'次ID切り出し' + m[0]:
'次パート無し');
return m && m[1] || '';
}
/**
* ページの遷移処理。実際にはコンテンツを入れ替えるだけで
* フロントのページは遷移させない
*/
function buffShift() {
$('.progressBar.buf .bar').css('width', '0%');
var $nextPage = $('#buf-video').contents().find('body');
// ビデオソース書き換え
var $buf = $nextPage.find('#kusa5 video');
if (!OPT.buffer || $buf.size() == 0) {
FullScreen.cancel();
return;
}
// 上部のコメントとかタイトル書き換え
$('.videoHeaderTitle').text($nextPage.find('.videoHeaderTitle').text());
$('#topVideoInfo').remove();
$('#videoDetailInformation').append($nextPage.find('#topVideoInfo'));
const nextid = $buf[0].dataset.smid;
const nobuffer = nextid <= $video.data('smid');
$video.attr('src', $buf.attr('src'));
$video.get(0).dataset.smid = nextid;
loadApiInfo(nextid).then(loadMsg); // メッセージ取得 && 整形登録
history.pushState(null,null, WATCH + nextid); // url書き換え
if (nobuffer) {
$('#buf-video').remove();
FullScreen.cancel();
} else {
setTimeout(()=>createBuf(getNextId()), 10000);
}
}
function createBuf(id) {
$('#buf-video').remove();
if (!id)
return;
$('#kusa5').append(`<iframe id="buf-video" src="${WATCH + id}"
width="10px" height="10px" />`);
// 次ページの動画読み込み進捗を取得
setTimeout(() => {
const v = $('#buf-video').contents().find('#kusa5 video')[0];
const p = $('.progressBar.buf .bar');
$(v).off('timeupdate').on('timeupdate', _.throttle(ev => {
var w = 100 * v.currentTime / v.duration;
p.css('width', w+'%');
}, 10000));
}, 20000);
}
function ngfilter(ch) {
if (ch.t < 100) // 1秒以内。いわゆる0秒コメ
return false;
// NGワード
return _.reduce([
/[韓荒\[\]]/,
/(くない|くせえ|アンチ|びみょ|チョン)/,
/(イライラ|いらいら)/,
/(キモ|きも|パク|ぱく|エミュ|ウザ|うざ)/,
/(うぜ|ウゼ)[えぇエェ]/,
/(推奨|注意|NG|NG|自演)/,
/(朝鮮|創価|在日)/,
/(イラ|いら)[イいつ]?/,
/(嫌|いや|イヤ)なら/,
/(ゆとり|信者|名人様|視聴者様|赤字|水色|餓鬼)/,
/(萎え|挙手)/,
/(つま|ツマ)[ラら]?[なねんナネン]/,
/(eco|eco|エコノミ|画質|時報|3DS|倍速)/,
/^[ノノ]$/,
/^[\//@@※←↑↓]/,
], (cary, re) => cary && !ch.c.match(re), true);
}
function xml2chats(xml) {
console.log("xmlは %s ", xml);
return _.chain($(xml).find('chat'))
.map(ch =>
({ t: $(ch).attr('vpos') -0, //cast
c: $(ch).text()}))
// .filter(ngfilter)
.sortBy(c => c.t);
}
function loadMsg(info) {
var getThreadKey;
debugger;
if(info.needs_key==1){
getThreadKey=$.ajax({
type:'GET',
url:`http://flapi.nicovideo.jp/api/getthreadkey?thread=${info.thread_id}&language_id=0`,
contentType: "text/plain",
dataType: 'text',
crossDomain: true,
cache: false,
xhrFields: {'withCredentials': true}
}).then(result =>{
info.threadkey=result.match(/threadkey=(.+?)&/)[1];
info.requestXml=`<packet><thread thread="${info.thread_id}" version="20090904" user_id="${info.user_id}" threadkey="${info.threadkey}" force_184="1" scores="1" nicoru="1" with_global="1"/><thread_leaves thread="${info.thread_id}" user_id="${info.user_id}" threadkey="${info.threadkey}" force_184="1" scores="1" nicoru="1">0-${Math.ceil(info.l/60)}:100,1000</thread_leaves></packet>`;
return info;
},data => console.log('メッセージロード失敗', data));
}else{
getThreadKey=new Promise(function(resolve, reject) {
info.requestXml=`<packet><thread thread="${info.thread_id}" version="20090904" user_id="${info.user_id}" scores="1" nicoru="1" with_global="1"/>
<thread_leaves thread="${info.thread_id}" user_id="${info.user_id}" scores="1" nicoru="1">0-${Math.ceil(info.l/60)}:1000,1000</thread_leaves>
<thread thread="${info.thread_id}" version="20061206" res_from="-5000" fork="1" click_revision="-1" scores="1"/></packet>`;
resolve(info);
});
}
return getThreadKey.then(info=>{return $.ajax({
type: 'POST',
url: info.ms,
// サーバーによってCORSで弾かれたりバッドリクエスト判定されたり
// するので application/xmlでもなくtext/xmlでもなく
// この値に落ち着いた
contentType: "text/plain",
dataType: 'xml',
data: info.requestXml,
crossDomain: true,
cache: false,
});}).then(
xml2chats,
data =>{console.log('メッセージロード失敗', data);}
).then(chats => {
// 時間イベントの発火で、対象メッセージがあれば流す
var lastT = 0;
// 次の動画への繊維などで複数回登録させるのでoff()
$video.off('timeupdate').on('timeupdate', _.throttle(ev => {
// chat.vpos is 1/100 sec.
var v = ev.target;
var t = Math.round(v.currentTime * 100);
chats.filter(ch => lastT < ch.t && ch.t < t)
.forEach(_.throttle(marqueeMsg, 50));
lastT = t;//更新
// ついでに動画の進捗バーを更新
var w = 100 * v.currentTime / v.duration; //in %
$('.progressBar.seek .mainbar').css('width', w+'%');
$('.controle-panel .current')
.text(UTIL.sec2HHMMSS(v.currentTime));
$('.controle-panel .duration')
.text(UTIL.sec2HHMMSS(v.duration));
}, 1000));
$video.off('progress').on('progress', _.throttle(ev => {
var v = ev.target;
if (v.buffered.length == 0)
return;
var bufTime = v.buffered.end(v.buffered.length-1);
var bw = 100 * bufTime / v.duration;
$('.progressBar.seek .bufferbar').css('width', bw+'%');
}, 1000));
});
}
/** 動画URLなどの情報を取得してPromiseを返す。
* キャリーされる値はクエリストリングをオブジェクトにした奴
*/
function loadApiInfo(id) {
return $.ajax({
'type': 'GET',
'url': ASKURL + id,
'crossDomain': true,
'cache': false,
'xhrFields': {'withCredentials': true} // Cookie認証が必要
}).then(qs => _.reduce(qs.split('&'), (o, k_v)=>{
var a = _.map(k_v.split('='), decodeURIComponent);
o[a[0]] = a[1];
return o; // クエリストリングをオブジェクトにした奴
},{}));
}
function marqueeMsg(ch) {
const baseW = $('#kusa5').width() + 10;
const hasMsg = $('#kusa5 .msg').size() > 0;
const $m = $('<span class="msg"/>').text(ch.c);
$m.css('transform', `translate3d(${baseW}px, 0, 0)`);
$video.after($m);
function hasRightSpace(l) {
// 一番右端にあるmsgの右端の位置
var bigwidth = _.max(_.map($('#kusa5').find(l),
// offsetLeftだと0が返る
l => $(l).position().left + l.scrollWidth));
var rigthSpace = baseW - bigwidth;
// 比率係数は適当。文字が重なるようなら要調整
// transition速度(つまりアニメーション再生時間)と関係
return rigthSpace > $m.width() * 0.45;
}
const line = !hasMsg || hasRightSpace('.l1') ? 'l1' :
hasRightSpace('.l2') ? 'l2' :
hasRightSpace('.l3') ? 'l3' :
hasRightSpace('.l4') ? 'l4' :
hasRightSpace('.l5') ? 'l5' :
hasRightSpace('.l6') ? 'l6' :
hasRightSpace('.l7') ? 'l7' :
hasRightSpace('.l8') ? 'l8' :
hasRightSpace('.l9') ? 'l9' :
hasRightSpace('.l10') ? 'l10' :
hasRightSpace('.l11') ? 'l11' :
hasRightSpace('.l12') ? 'l12' :
'l13';
$m.addClass(line);
//オーバーシュート
$m.css('transform', `translate3d(-${$m.width() + 10}px, 0, 0)`);
//アニメ停止で自動削除
$m.on('transitionend', ev => $(ev.target).remove());
}
var UTIL = {};
UTIL.sec2HHMMSS = function (sec) {
var sec_num = parseInt(sec, 10); // don't forget the second param
var hours = Math.floor(sec_num / 3600);
var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
var seconds = sec_num - (hours * 3600) - (minutes * 60);
if (hours < 10) {hours = "0"+hours;}
if (minutes < 10) {minutes = "0"+minutes;}
if (seconds < 10) {seconds = "0"+seconds;}
return (hours > 0? hours+':' :'') + minutes+':'+seconds;
};
const FullScreen = {};
FullScreen.isOpen = () =>
document.mozFullScreen || document.webkitIsFullScreen ||
(document.fullScreenElement && document.fullScreenElement !== null);
FullScreen.req = (e) =>
!!e.mozRequestFullScreen && e.mozRequestFullScreen() ||
!!e.requestFullScreen && e.requestFullScreen() ||
!!e.webkitRequestFullScreen && e.webkitRequestFullScreen();
FullScreen.cancel = () =>
!!document.mozCancelFullScreen && document.mozCancelFullScreen() ||
!!document.cancelFullScreen && document.cancelFullScreen() ||
!!document.webkitCancelFullScreen && document.webkitCancelFullScreen();
FullScreen.toggle = () =>
FullScreen.isOpen() ?
FullScreen.cancel() :
FullScreen.req($('#kusa5')[0]);
function rateForm() {
//var rd = [1, 1.2, 1.5, 2, 2.2, 2.5, 3]
var rd = [0.5,1, 1.3463246851125779, 1.6678900230322302,
1.9680012082762555, 2.249342814692259, 2.514125064795459,
2.764189394992108, 3.001086195676507,3.5,4.0 ]
.map(v=>
`<input name="nicorate" type="radio" id="rd${v}"
class="btn" value="${v}">
<label for="rd${v}">${v.toFixed(1)}<span>x</span></label>`);
return `<div class="ratepanel">${rd.join('')}</div>`;
}
const COMMENT = `
<div class="comment">
<input type="text" class="l" /><button class="btn l">投稿</button>
</div>`;
const CONTROLE_PANEL = `
<div class="controle-panel">
<div class="progressBar seek">
<span class="bufferbar"/>
<span class="mainbar"/>
</div>
<div class="progressBar buf"><span class="bar"/></div>
<button class="btn toggle ico play"></button>
${rateForm()}
<button class="btn full ico r"></button>
<button class="btn volume volumeup ico r"><span class="volume-num"></span></button>
<button class="btn volume volumedown ico r"></button>
<button class="btn comment-hidden ico r"></button>
<div class="playtime r">
<span class="current"></span>
/
<span class="duration"></span>
</div>
</div>`;
function ctrPanel() {
var $panel = $(CONTROLE_PANEL);
$panel.find('.btn.full').click(FullScreen.toggle);
$panel.find('.btn.toggle').click($video.videoToggle);
return $panel;
}
//update Progress Bar control
var updatebar = function(e) {
var bar = $('.progressBar.seek');
var offset = e.pageX - bar.offset().left; //Click pos
var ratio = Math.min(1, Math.max(0, offset / bar.width()));
//Update bar and video currenttime
$('.progressBar.seek .mainbar').css('width', (ratio * 100)+'%');
$video[0].currentTime = $video[0].duration * ratio;
return true;
}
/** main というかエントリーポイント */
;(function () {
const kusa5 = $('#kusa5')
.append($video)
.append(ctrPanel());
$('input[name=nicorate]').change(ev => {
localStorage.nicoRate =
$video.get(0).playbackRate = parseFloat($(ev.target).val());
});
$('input[value="'+ localStorage.nicoRate +'"]').click();
$('#kusa5 button.volumeup').click(ev => {
localStorage.nicoVolume = localStorage.nicoVolume++ % 5 + 1;
$('#kusa5 span.volume-num').text(localStorage.nicoVolume);
$video[0].volume = localStorage.nicoVolume * 0.2;
});
$('#kusa5 button.volumedown').click(ev => {
localStorage.nicoVolume = localStorage.nicoVolume++ % 5 - 1;
$('#kusa5 span.volume-num').text(localStorage.nicoVolume);
$video[0].volume = localStorage.nicoVolume * 0.2;
});
// デフォルトボリュームの表示
localStorage.nicoVolume = localStorage.nicoVolume || 5;
$('#kusa5 span.volume-num').text(localStorage.nicoVolume);
$('#kusa5 button.comment-hidden')
.click(ev => kusa5.toggleClass('comment-hidden'));
var promise = loadApiInfo(launchID).then(info => {
$video.attr('src', info.url);
$video.get(0).dataset.smid = launchID;
return info;
});
if (isIframe)
return; // 以降はフォワードページのみの処理
/* シークバーのドラッグ処理*/
var timeDrag = false; /* Drag status */
$('.progressBar.seek').mousedown(function(e) {
timeDrag = true;
updatebar(e);
});
$(document).mouseup(function(e) {
if(!timeDrag)
return;
timeDrag = false;
updatebar(e.pageX);
}).mousemove(e=> timeDrag && updatebar(e));
// ボタン押された時の動作登録
var keyTbl = [];
keyTbl[32] = $video.videoToggle; //スペースキー
kusa5.keyup(e => {
if (!keyTbl[e.keyCode])
return;
keyTbl[e.keyCode]();
e.preventDefault();
});
kusa5.keydown(e => {
//ボタンの処理が登録されてたらブラウザの動作をうちけす
if (keyTbl[e.keyCode])
e.preventDefault();
});
//メッセージ取得、文字流しとかのループイベント登録
promise.then(loadMsg);
if (OPT.buffer) // バッファ用のiFrameを作成する
setTimeout(() => createBuf(getNextId()), 10000);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment