Last active
March 10, 2016 13:16
-
-
Save BtbN/378340fc2372e5b96163 to your computer and use it in GitHub Desktop.
Replace Twitch.tv Video player with HTML5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Twitch hls.js | |
// @namespace http://btbn.de/ | |
// @version 6.7 | |
// @license MIT | |
// @description Twitch hls.js replacer | |
// @author BtbN | |
// @include *.twitch.tv/* | |
// @exclude api.twitch.tv/* | |
// @exclude blog.twitch.tv/* | |
// @exclude help.twitch.tv/* | |
// @exclude *.www.twitch.tv/* | |
// @require https://raw.githubusercontent.com/dailymotion/hls.js/master/dist/hls.min.js | |
// @updateURL https://gist.githubusercontent.com/BtbN/378340fc2372e5b96163/raw/twitchhtml5.user.js | |
// @downloadURL https://gist.githubusercontent.com/BtbN/378340fc2372e5b96163/raw/twitchhtml5.user.js | |
// @grant none | |
// ==/UserScript== | |
'use strict'; | |
var hls_error_counter = 0; | |
if(window.top == window.self) | |
pagereload(); | |
function pagereload() | |
{ | |
if(!Hls.isSupported()) | |
return; | |
decErrorCounter(); | |
document.documentElement.addEventListener('DOMSubtreeModified', checkload, false); | |
} | |
function decErrorCounter() | |
{ | |
if(hls_error_counter > 0) | |
hls_error_counter -= 1; | |
setTimeout(decErrorCounter, 5000); | |
} | |
function checkNeedReplace() | |
{ | |
// Not a simple stream page, prevent breaking VOD player and stuff | |
if(window.location.pathname.split('/').length != 2) | |
return false; | |
return !document.getElementById('html5player'); | |
} | |
function getStreamName() | |
{ | |
if (document.getElementById("hostmode")) | |
{ | |
var hostnode = document.querySelector("a[data-tt_content=host_channel]"); | |
if(!hostnode) | |
return false; | |
var prof = hostnode.href.split('/'); | |
return prof[prof.length - 1]; | |
} | |
else | |
{ | |
var profilenode = document.getElementsByClassName('channel-name')[0]; | |
if(!profilenode) | |
return false; | |
var prof = profilenode.href.split('/'); | |
return prof[prof.length - 2]; | |
} | |
} | |
function killVideo() | |
{ | |
var video = document.getElementById('html5player'); | |
if(!video) | |
return; | |
//This triggers a reload because DOM modified | |
video.parentNode.removeChild(video); | |
} | |
function removeJunk() | |
{ | |
var i; | |
if(window.location.pathname.split('/').length != 2) | |
return; | |
var player_errors = document.getElementsByClassName('player-error'); | |
for(i = 0; i < player_errors.length; i++) { | |
var player_error = player_errors[i]; | |
setTimeout(function() { player_error.parentNode.removeChild(player_error); }, 0); | |
} | |
var player_objs = document.querySelectorAll('[id^=swfobject]'); | |
for(i = 0; i < player_objs.length; i++) { | |
var player_obj = player_objs[i]; | |
setTimeout(function() { player_obj.parentNode.removeChild(player_obj); }, 0); | |
} | |
var player_ctrls = document.getElementsByClassName('player-controls-bottom'); | |
for(i = 0; i < player_ctrls.length; i++) { | |
var player_ctrl = player_ctrls[i]; | |
setTimeout(function() { player_ctrl.parentNode.removeChild(player_ctrl); }, 0); | |
} | |
} | |
function checkload() | |
{ | |
var officialPlayer = true; // CHANGE THIS to true, if you want to iframe the official HTML5 player instead | |
if(!document.getElementById('oldtheamodebtn')) | |
{ | |
document.documentElement.removeEventListener('DOMSubtreeModified', checkload, false); | |
var chan_actions = document.getElementsByClassName('channel-actions')[0]; | |
if(chan_actions) { | |
var thea_button = document.createElement('span'); | |
thea_button.className='ember-view'; | |
var thea_sub_button = document.createElement('span'); | |
thea_sub_button.id = 'oldtheamodebtn'; | |
thea_sub_button.className = 'button action js-control-tip'; | |
thea_sub_button.innerHTML = 'Theatre'; | |
thea_sub_button.onclick = function() { App.__container__.lookup('controller:channel').send('toggleTheatre'); } | |
thea_button.appendChild(thea_sub_button); | |
chan_actions.appendChild(thea_button); | |
} | |
document.documentElement.addEventListener('DOMSubtreeModified', checkload, false); | |
} | |
removeJunk(); | |
if(checkNeedReplace()) | |
{ | |
var video = 0; | |
var streamer = getStreamName(); | |
if(!streamer) | |
return; | |
document.documentElement.removeEventListener('DOMSubtreeModified', checkload, false); | |
if(!officialPlayer) { | |
var player = document.getElementsByClassName('dynamic-player')[0]; | |
if(!player) | |
return; | |
player.innerHTML = '<video id="html5player" width="100%" height="100%"></video>'; | |
video = document.getElementById('html5player'); | |
document.documentElement.addEventListener('DOMSubtreeModified', checkload, false); | |
} else { | |
var player = document.getElementById('video-1'); | |
if(!player) | |
return; | |
player.innerHTML = '<iframe id="html5player" src="https://player.twitch.tv/?branding=false&html5&showInfo=false&channel=' + streamer + '" width="100%" height="100%" allowfullscreen="allowfullscreen" webkitallowfullscreen="webkitallowfullscreen" mozallowfullscreen="mozallowfullscreen"></iframe>'; | |
document.documentElement.addEventListener('DOMSubtreeModified', checkload, false); | |
return; | |
} | |
get_stream_url(streamer, function(url) { | |
var hls = new Hls(); | |
hls.on(Hls.Events.MANIFEST_PARSED,function() { | |
video.play(); | |
}); | |
hls.on(Hls.Events.ERROR, function(event,data) { | |
if(data.type == Hls.ErrorTypes.NETWORK_ERROR) | |
hls_error_counter += 1; | |
if(hls_error_counter >= 3 || data.fatal) { | |
video.pause(); | |
hls.destroy(); | |
killVideo(); | |
} | |
}); | |
hls.loadSource(url); | |
hls.attachMedia(video); | |
}); | |
} | |
} | |
function get_stream_url(streamer, cb) | |
{ | |
var auth_url = "https://api.twitch.tv/api/channels/" + streamer + "/access_token"; | |
$.get(auth_url, function(data) { | |
var token = data['token']; | |
var sig = data['sig']; | |
var params = { | |
player: "twitchweb", | |
token: token, | |
sig: sig, | |
allow_audio_only: "false", | |
allow_source: "true", | |
type: "any" | |
}; | |
var pp = $.param(params) | |
var usher_url = "https://usher.ttvnw.net/api/channel/hls/" + streamer + ".m3u8?" + pp | |
get_source_url(usher_url, cb); | |
}); | |
} | |
function get_source_url(usher_url, cb) | |
{ | |
var streamId = 0; ///CHANGE THIS, if you want a diffrent quality level. | |
/// -1 is auto-select, 0 is source, 1 is high, 2 medium, 3 low. | |
/// Falls back to auto-select in case of invalid ID | |
var localStreamId = 0; | |
if(streamId < 0) { | |
cb(usher_url); | |
return; | |
} | |
$.get(usher_url, function(data) { | |
data.split("\n").some(function(l) { | |
if(stringStartsWith(l, "http://") || stringStartsWith(l, "https://")) { | |
localStreamId += 1; | |
if(localStreamId <= streamId) | |
return false; | |
cb(l); | |
return true; | |
} | |
return false; | |
}); | |
if(localStreamId <= streamId) | |
cb(usher_url); | |
}).fail(function() { | |
setTimeout(killVideo, 5000); | |
}); | |
} | |
function stringStartsWith(string, prefix) { | |
return string.slice(0, prefix.length) == prefix; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment