Greasy Fork

Greasy Fork is available in English.

bilibili 直播 HTML5 播放器

B 站的直播的 HTML5 播放器

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         bilibili 直播 HTML5 播放器
// @namespace    https://www.kindjeff.com/
// @version      2017.2.24
// @description  B 站的直播的 HTML5 播放器
// @author       kindJeff
// @match        http://live.bilibili.com/*
// @match        https://live.bilibili.com/*
// @require      https://cdn.bootcss.com/hls.js/0.6.21/hls.min.js
// @require      https://cdn.rawgit.com/weizhenye/Danmaku/b2ae44f7dcd28b27c31e5db5b512b9474d0396c7/dist/danmaku.min.js
// @run-at       document-end
// ==/UserScript==

var room_id;

setTimeout(function(){
    //var xhr = new XMLHttpRequest();
    //xhr.onreadystatechange=function(){
    //    if (xhr.readyState==4 && xhr.status==200){
    //        eval(xhr.responseText);
            var link = $('#player_object').children('[name="flashvars"]').val();
            room_id = link.match(/cid=.*?&/)[0].slice(4,-1);
            get_url_and_replace_player(room_id);
            init_danmaku();
            set_danmu_control();
            click_list();
    //    }
    //};
    //xhr.open('GET', 'https://raw.githubusercontent.com/weizhenye/Danmaku/master/dist/danmaku.min.js');
    //xhr.send();
}, 2000);


function get_url_and_replace_player(room_id){
    var api_url = 'https://api.live.bilibili.com/api/playurl?platform=h5&cid=' + room_id;
    $.ajax({
        url: api_url,
        type: "GET",
        dataType: 'json',
        success: function(data){
            replace_player(data.data);

            if(window.df_danmu_ws!==undefined){
                window.i_close_it_myself = true;
                window.df_danmu_ws.close();
                window.df_danmu_ws = undefined;
            }
            var df_domain = 'broadcastlv.chat.bilibili.com';
            var df_portobj = {'ws':7170, 'wss':7172};
            window.df_danmu_ws = new DanmuSocket(parseInt(room_id), df_domain, df_portobj);
            window.df_danmu_ws.setListener(danmuListener);
        }
    });
}

function replace_player(m3u8_url){
    var w = $('#js-player-decorator').width();
    var h = $('#js-player-decorator').height();

    remove_player();

    var player = document.createElement('video');
    player.id = 'h5_player';
    player.style.width = '100%';
    player.style.height = '100%';
    player.style.position = 'absolute';
    player.setAttribute('controls', 'controls');
    document.getElementById('js-player-decorator').appendChild(player);

    if(Hls.isSupported()) {
        var video = document.getElementById('h5_player');
        var hls = new Hls();
        hls.loadSource(m3u8_url);
        hls.attachMedia(video);
        hls.on(Hls.Events.MANIFEST_PARSED,function() {
          video.play();
        });
    }
}

function remove_player(){
    var flash_player = document.getElementById('player_object');
    if(flash_player!==null)
        flash_player.remove();

    var html5_player = document.getElementById('h5_player');
    if(html5_player!==null)
        html5_player.remove();
}

function click_list(){
    if(window.location.pathname==='/'){
        $($('[role="list"]')[0]).children().on('click', function(){
            var room_id = $(this).attr('data-cid');
            get_url_and_replace_player(room_id);
        });
    }
}


/* danmaku */
const rawHeaderLen = 16;
const packetOffset = 0;
const headerOffset = 4;
const verOffset = 6;
const opOffset = 8;
const seqOffset = 12;
var pako = window.pako;
var textDecoder = getDecoder(true);
var textEncoder = getEncoder();
var heartbeatInterval;

function getDecoder (isUseful) {
    if(window['TextDecoder'] && isUseful) {
        return new window['TextDecoder']();
    } else {
        return {
            decode: (buf) => {
                return decodeURIComponent(window.escape(String.fromCharCode.apply(null, new Uint8Array(buf))));
            }
        }
    }
}

function getEncoder () {
    if(window['TextEncoder']) {
        return new window['TextEncoder']();
    } else {
        return {
            encode: (str) => {
                let buf = new ArrayBuffer(str.length);
                let bufView = new Uint8Array(buf);
                for (let i = 0, strlen = str.length; i < strlen; i++) {
                    bufView[i] = str.charCodeAt(i);
                }
                return bufView;
            }
        }
    }
}

function mergeArrayBuffer(ab1, ab2) {
    var u81 = new Uint8Array(ab1),
        u82 = new Uint8Array(ab2),
        res = new Uint8Array(ab1.byteLength + ab2.byteLength);
    res.set(u81, 0);
    res.set(u82, ab1.byteLength);
    return res.buffer;
}

class DanmuSocket {

    constructor (roomid,domain,portobj) {
        const ws = window.location.protocol.indexOf('https') > -1 ? 'wss' : 'ws';
        const port = portobj[ws];
        this.connection = new WebSocket(ws + "://"+ domain +":"+ port +"/sub");
        this.connection.binaryType = 'arraybuffer';
        this.connection.onopen = this.firstConnection.bind(this);
        this.connection.onmessage = onMessage.bind(this);
        this.connection.onclose = onClose.bind(this);
        this.connection.onerror = onError.bind(this);
        this.roomid = roomid
    }

    firstConnection () {
        console.log("Danmu WebSocket Server Connected.");
        console.log("Handshaking...");
        var token = JSON.stringify({
            'uid': 0,
            'roomid': this.roomid
        });
        var headerBuf = new ArrayBuffer(rawHeaderLen);
        var headerView = new DataView(headerBuf, 0);
        var bodyBuf = textEncoder.encode(token);
        headerView.setInt32(packetOffset, rawHeaderLen + bodyBuf.byteLength);
        headerView.setInt16(headerOffset, rawHeaderLen);
        headerView.setInt16(verOffset, 1);
        headerView.setInt32(opOffset, 7);
        headerView.setInt32(seqOffset, 1);
        this.connection.send(mergeArrayBuffer(headerBuf, bodyBuf));
    }

    heartBeat () {
        var headerBuf = new ArrayBuffer(rawHeaderLen);
        var headerView = new DataView(headerBuf, 0);
        headerView.setInt32(packetOffset, rawHeaderLen);
        headerView.setInt16(headerOffset, rawHeaderLen);
        headerView.setInt16(verOffset, 1);
        headerView.setInt32(opOffset, 2);
        headerView.setInt32(seqOffset, 1);
        this.connection.send(headerBuf);
    }

    closeHeartBeat () {
        clearInterval(this.heartBeating);
    }

    send (data) {
        this.connection.send(data);
    }

    close () {
        this.connection.close();
    }

    setListener (listener) {
        this._listener = listener;
    }

}

function onMessage (evt) {
    var data = evt.data;
    var dataView = new DataView(data, 0);
    var packetLen = dataView.getInt32(packetOffset);
    var headerLen = dataView.getInt16(headerOffset);
    var ver = dataView.getInt16(verOffset);
    var op = dataView.getInt32(opOffset);
    var seq = dataView.getInt32(seqOffset);

    switch(op) {
        case 8:
            this.heartBeat();
            heartbeatInterval = setInterval(this.heartBeat.bind(this), 30 * 1000);
        break;
        case 3:
            // console.log("online: " + dataView.getInt32(16));
            if (this._listener) this._listener('online', dataView.getInt32(16));
        break;
        case 5:
            var packetView = dataView;
            var msg = data;
            var msgBody;
            for (var offset=0; offset<msg.byteLength; offset+=packetLen) {
                packetLen = packetView.getInt32(offset);
                headerLen = packetView.getInt16(offset+headerOffset);
                msgBody = textDecoder.decode(msg.slice(offset+headerLen, offset+packetLen));
                if (!msgBody) {
                    textDecoder = getDecoder(false);
                    msgBody = textDecoder.decode(msg.slice(offset+headerLen, offset+packetLen));
                }
                if (this._listener) this._listener('msg', msgBody);
            }
        break;
    }
}

function onClose () {
    if (heartbeatInterval) clearInterval(heartbeatInterval);
    if(! i_close_it_myself){
        var delay = Math.floor(Math.random() * (6 - 3) + 3);
        setTimeout(this.firstConnection.bind(this), delay * 1000);
        console.log(delay);
    }
    i_close_it_myself = false;
}

function onError () {
    console.log("Client Error.");
}


/*******************/
function change_online(online) {
    $('span.v-bottom').text(online + ' 人');
}

function emit_danmu(data) {
    if(data.cmd==='DANMU_MSG'){
        var msg = data.info[1];
        window.df_danmaku.emit({
            text: msg,
            canvasStyle: {
                font: data.info[0][2]+'px sans-serif',
                textAlign: 'start',
                textBaseline: 'bottom',
                direction: 'inherit',
                fillStyle: '#fff',
                strokeStyle: '#000',
                lineWidth: 1.2,
                shadowBlur: 0,
                shadowColor: '#000',
                shadowOffsetX: 0,
                shadowOffsetY: 0,
                filter: 'none',
                globalAlpha: 1.0
            }
        })
    }else if(data.cmd==='WELCOME'){

    }else if(data.cmd==='SEND_GIFT'){

    }
}

function append_danmu(data) {
    if(data.cmd==='DANMU_MSG'){
        var u_name = data.info[2][1];
        var uid = data.info[2][0];
        var lv = data.info[4][0];
        var rank = data.info[4][3]; if(typeof(rank)=='string'&&rank.indexOf('>')!==-1) {rank.replace('>', '&gt;')}
        var msg = data.info[1];
        // console.log(u_name,uid,lv,rank,msg);
        var comment_div = '<div class="msg-item-ctnr"><div class="chat-msg " data-uname="'+u_name+'" data-uid="'+uid+'"><div class="user-level-icon lv-'+lv+'"> UL '+lv+' <div class="user-level-info"><p>用户等级:'+lv+'</p><p><a href="http://live.bilibili.com/rank" target="_blank">排名:'+rank+'</a></p></div></div><span class="user-name color">'+u_name+' : </span><span class="msg-content">'+msg+'</span></div></div>';
        $(comment_div).appendTo('#chat-msg-list');
        if($('#chat-msg-list').children().length>100)
            $('#chat-msg-list').children(':first').remove();
        $("#chat-msg-list").scrollTop($("#chat-msg-list")[0].scrollHeight);
    }
}

function danmuListener(content_type, content){
    if(content_type==='online'){
        if(window.dom_changed===undefined){
            $('#h5_player').prev().appendTo('#js-player-decorator');
            window.dom_changed = true;
        }
        change_online(content);
    }else if(content_type==='msg'){
        var content_obj = JSON.parse(content);
        emit_danmu(content_obj);
        append_danmu(content_obj);
    }
}

function init_danmaku() {
    window.df_danmaku = new Danmaku();
    df_danmaku.init({
        container: $('#js-player-decorator')[0],
        video: $("#h5_player")[0],
        engine:'canvas'
    });
    $('canvas')[0].style.position = 'absolute';

    // send danmu
    function send_danmu(){
        var msg = $("#df-danmu-textbox").val();
        var xhr = new XMLHttpRequest();
        // xhr.setRequestHeader('X-Cookie', document.cookie);
        xhr.open('POST', 'http://live.bilibili.com/msg/send');
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        xhr.send($.param({
            color: 16777215,
            fontsize: 25,
            mode: 1,
            msg: msg,
            rnd: Math.floor(Date.now() / 1000),
            roomid: room_id
        }));
    }
    $("#danmu-textbox").off('keypress');
    $("#danmu-textbox").off('keyup');
    $("#danmu-textbox").off('keydown');
    $("#danmu-send-btn").off('click');

    $("#danmu-textbox")[0].id = 'df-danmu-textbox';
    $("#danmu-send-btn")[0].id = 'df-danmu-send-btn';
    $("#df-danmu-textbox").on('keyup', function (e) {
        if(e.keyCode == 13){
            send_danmu();
            $("#df-danmu-textbox").val('');
            e.preventDefault();
            return false;
        }
        return true;
    });
    $("#df-danmu-send-btn").on('click', function (e) {
        e.preventDefault();
        send_danmu();
        $("#df-danmu-textbox").val('');
    });
}

function set_danmu_control(){
    if(location.pathname==='/'){
        return;
    }

    var control_btn = $("<button>关闭弹幕</button>");
    control_btn.css('border-radius', '5px');
    control_btn.css('font-size', '12px');
    control_btn.height('21px');
    $('.room-info.tag-ctnr.v-top').children().remove();
    control_btn.appendTo('.room-info.tag-ctnr.v-top');
    control_btn.on('click', function () {
        if(control_btn.text()=='打开弹幕'){
            control_btn.text('关闭弹幕');
            $('canvas')[0].style.display = 'block';
        }else{
            $('canvas')[0].style.display = 'none';
            control_btn.text('打开弹幕');
        }
    });
}