1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-07-26 20:48:30 +00:00
FrankerFaceZ/src/ember/channel.js

1441 lines
42 KiB
JavaScript
Raw Normal View History

var FFZ = window.FrankerFaceZ,
utils = require('../utils'),
constants = require('../constants');
// --------------------
// Initialization
// --------------------
FFZ.prototype.setup_channel = function() {
2015-07-04 17:06:36 -04:00
// Style Stuff!
this.log("Creating channel style element.");
var s = this._channel_style = document.createElement("style");
s.id = "ffz-channel-css";
document.head.appendChild(s);
2015-06-05 03:59:28 -04:00
// Settings stuff!
document.body.classList.toggle("ffz-hide-view-count", !this.settings.channel_views);
document.body.classList.toggle('ffz-theater-stats', this.settings.theater_stats);
var banner_hidden = this.settings.hide_channel_banner;
banner_hidden = banner_hidden === 1 ? this.settings.channel_bar_bottom : banner_hidden > 0;
utils.toggle_cls('ffz-hide-channel-banner')(banner_hidden);
utils.toggle_cls('ffz-channel-bar-bottom')(this.settings.channel_bar_bottom);
2016-09-30 13:09:03 -04:00
utils.toggle_cls('ffz-minimal-channel-title')(this.settings.channel_title_top === 2);
utils.toggle_cls('ffz-channel-title-top')(this.settings.channel_title_top > 0);
utils.toggle_cls('ffz-minimal-channel-bar')(this.settings.channel_bar_collapse);
2016-09-30 13:09:03 -04:00
this.log("Hooking the Ember Channel Index redesign.");
this.update_views('component:channel-redesign', this.modify_channel_redesign);
this.update_views('component:channel-redesign/live', this.modify_channel_live);
2016-09-30 13:09:03 -04:00
this.log("Hooking the Ember Channel Index component.");
if ( ! this.update_views('component:legacy-channel', this.modify_channel_index) )
return;
var f = this,
Channel = utils.ember_lookup('controller:channel');
if ( ! Channel )
return f.error("Unable to find the Ember Channel controller");
2015-06-05 03:59:28 -04:00
this.log("Hooking the Ember Channel controller.");
Channel.reopen({
/*isEditable: function() {
var channel_id = this.get('content.id'),
user = this.get('login.userData');
if ( ! user || ! user.login )
return false;
else if ( user.login === channel_id || user.is_admin || user.is_staff)
return true;
// Okay, have we loaded this user's editor status? Try that.
if ( f._editor_of )
return f._editor_of.indexOf(channel_id) !== -1;
var t = this;
f.get_user_editor_of().then(function(result) {
// Once editor status is loaded, if the user does have editor
// status for this channel, update this property.
if ( result.indexOf(channel_id) !== -1 )
Ember.propertyDidChange(t, 'isEditable');
});
return false;
}.property('content.id', 'login.userData', 'login.userData.login'),*/
2016-09-30 13:09:03 -04:00
/*ffzUpdateUptime: function() {
2015-06-05 03:59:28 -04:00
if ( f._cindex )
f._cindex.ffzUpdateUptime();
2016-09-30 13:09:03 -04:00
}.observes("isLive", "channel.id"),*/
ffzUpdateInfo: function() {
if ( this._ffz_update_timer )
clearTimeout(this._ffz_update_timer);
2016-09-30 13:09:03 -04:00
if ( ! this.get('channel.id') )
return;
this._ffz_update_timer = setTimeout(this.ffzCheckUpdate.bind(this), 55000 + (Math.random() * 10000));
2016-09-30 13:09:03 -04:00
}.observes("channel.id"),
ffzCheckUpdate: function() {
var t = this,
2016-09-30 13:09:03 -04:00
id = t.get('channel.id');
id && utils.api.get("streams/" + id, {}, {version:3})
.done(function(data) {
if ( ! data || ! data.stream ) {
// If the stream is offline, clear its created_at time and set it to zero viewers.
2016-09-30 13:09:03 -04:00
t.set('channel.stream.createdAt', null);
t.set('channel.stream.viewers', 0);
return;
}
2016-09-30 13:09:03 -04:00
t.set('channel.stream.createdAt', utils.parse_date(data.stream.created_at) || null);
t.set('channel.stream.viewers', data.stream.viewers || 0);
var game = data.stream.game || (data.stream.channel && data.stream.channel.game);
if ( game ) {
2016-09-30 13:09:03 -04:00
t.set('channel.game', game);
}
if ( data.stream.channel ) {
if ( data.stream.channel.status )
2016-09-30 13:09:03 -04:00
t.set('channel.status', data.stream.channel.status);
if ( data.stream.channel.views )
2016-09-30 13:09:03 -04:00
t.set('channel.views', data.stream.channel.views);
2016-09-30 13:09:03 -04:00
if ( data.stream.channel.followers && t.get('channel.followers.isLoaded') )
t.set('channel.followers.total', data.stream.channel.followers);
}
})
.always(function(data) {
t.ffzUpdateInfo();
});
},
2015-06-10 18:46:04 -04:00
ffzHostTarget: function() {
2016-09-30 13:09:03 -04:00
var target = this.get('channel.hostModeTarget'),
2015-06-05 03:59:28 -04:00
name = target && target.get('name'),
2015-07-04 17:06:36 -04:00
id = target && target.get('id'),
2015-06-05 03:59:28 -04:00
display_name = target && target.get('display_name');
2015-07-04 17:06:36 -04:00
if ( id !== f.__old_host_target ) {
if ( f.__old_host_target )
f.ws_send("unsub", "channel." + f.__old_host_target);
2015-07-04 17:06:36 -04:00
if ( id ) {
f.ws_send("sub", "channel." + id);
2015-07-04 17:06:36 -04:00
f.__old_host_target = id;
} else
delete f.__old_host_target;
}
2015-06-05 03:59:28 -04:00
if ( display_name )
FFZ.capitalization[name] = [display_name, Date.now()];
if ( f._chatv )
f._chatv.ffzUpdateHost(target);
2015-07-04 17:06:36 -04:00
if ( f.settings.follow_buttons )
f.rebuild_following_ui();
if ( f.settings.srl_races )
f.rebuild_race_ui();
2016-09-30 13:09:03 -04:00
}.observes("channel.hostModeTarget")
2015-06-05 03:59:28 -04:00
});
Channel.ffzUpdateInfo();
2015-06-05 03:59:28 -04:00
}
FFZ.prototype.modify_channel_live = function(view) {
2016-09-30 13:09:03 -04:00
var f = this;
utils.ember_reopen_view(view, {
ffz_host: null,
2016-09-30 13:09:03 -04:00
ffz_init: function() {
var channel_id = this.get("channel.id"),
el = this.get("element");
f._cindex = this;
f.ws_sub("channel." + channel_id);
2016-09-30 13:09:03 -04:00
this.ffzUpdateAttributes();
2016-09-30 13:09:03 -04:00
this.ffzFixTitle();
this.ffzUpdateUptime();
this.ffzUpdateChatters();
this.ffzUpdateHostButton();
this.ffzUpdatePlayerStats();
if ( f.settings.auto_theater ) {
var player = f.players && f.players[channel_id] && f.players[channel_id].get('player');
if ( player )
player.setTheatre(true);
}
this.$().on("click", ".ffz-creative-tag-link", function(e) {
if ( e.button !== 0 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey )
return;
utils.ember_lookup("router:main").transitionTo('creative.hashtag.index', this.getAttribute('data-tag'));
e.preventDefault();
return false;
});
},
ffzUpdateAttributes: function() {
var channel_id = this.get("channel.id"),
hosted_id = this.get("channel.hostModeTarget.id"),
el = this.get("element");
2016-09-30 13:09:03 -04:00
if ( hosted_id !== this.ffz_host ) {
if ( this.ffz_host )
f.ws_unsub("channel." + this.ffz_host);
2016-09-30 13:09:03 -04:00
if ( hosted_id )
f.ws_sub("channel." + hosted_id);
this.ffz_host = hosted_id;
}
2016-09-30 13:09:03 -04:00
el.classList.add('ffz-channel');
el.classList.toggle('ffz-host', hosted_id || false);
el.setAttribute('data-channel', channel_id || '');
el.setAttribute('data-hosted', hosted_id || '');
}.observes('channel.id', 'channel.hostModeTarget'),
ffz_destroy: function() {
var channel_id = this.get("channel.id"),
el = this.get("element");
if ( channel_id )
f.ws_unsub("channel." + channel_id);
if ( this.ffz_host ) {
f.ws_unsub("channel." + this.ffz_host);
this.ffz_host = null;
}
if ( f._cindex === this )
f._cindex = null;
if ( this._ffz_update_uptime )
clearTimeout(this._ffz_update_uptime);
if ( this._ffz_update_stats )
clearTimeout(this._ffz_update_stats);
document.body.classList.remove('ffz-small-player');
utils.update_css(f._channel_style, channel_id, null);
},
2016-09-30 13:09:03 -04:00
ffzFixTitle: function() {
if ( ! f.settings.stream_title )
return;
var channel_id = this.get("channel.id"),
status = this.get("channel.status"),
game = this.get("channel.game"),
tokens = f.tokenize_line(channel_id, channel_id, status, true);
if ( game === 'Creative' )
tokens = f.tokenize_ctags(tokens);
var el = this.$(".cn-metabar__title .card__title");
el && el.html(f.render_tokens(tokens));
}.observes('channel.id', 'channel.status', 'channel.game'),
ffzUpdateUptime: function() {
if ( this._ffz_update_uptime ) {
clearTimeout(this._ffz_update_uptime);
delete this._ffz_update_uptime;
}
var container = this.get('element');
if ( this.isDestroyed || ! container || ! f.settings.stream_uptime || ! this.get('isLiveAccordingToKraken') )
return container && this.$("#ffz-uptime-display").remove();
// Schedule an update.
this._ffz_update_uptime = setTimeout(this.ffzUpdateUptime.bind(this), 1000);
// Determine when the channel last went live.
var online = this.get("channel.stream.createdAt"),
now = Date.now() - (f._ws_server_offset || 0);
var uptime = online && Math.floor((now - online.getTime()) / 1000) || -1;
if ( uptime < 0 )
return this.$("#ffz-uptime-display").remove();
var el = container.querySelector('#ffz-uptime-display span');
if ( ! el ) {
var cont = container.querySelector('.cn-metabar__more');
if ( ! cont )
return;
var stat = utils.createElement('span'),
figure = utils.createElement('figure', 'icon cn-metabar__icon', constants.CLOCK + ' '),
balloon = utils.createElement('div', 'balloon balloon--tooltip balloon--down balloon--center'),
balloon_wrapper = utils.createElement('div', 'balloon-wrapper', figure),
stat_wrapper = utils.createElement('div', 'cn-metabar__ffz flex__item mg-l-1', balloon_wrapper);
balloon_wrapper.appendChild(stat);
balloon_wrapper.appendChild(balloon);
stat_wrapper.id = 'ffz-uptime-display';
balloon.innerHTML = 'Stream Uptime <nobr>(since ' + online.toLocaleString() + ')</nobr>';
var viewers = cont.querySelector(".cn-metabar__livecount");
if ( viewers )
cont.insertBefore(stat_wrapper, viewers.nextSibling);
else
cont.appendChild(stat_wrapper);
el = stat;
}
el.innerHTML = utils.time_to_string(uptime, false, false, false, f.settings.stream_uptime === 1 || f.settings.stream_uptime === 3);
}.observes('channel.stream.createdAt', 'isLiveAccordingToKraken'),
ffzUpdatePlayerStats: function() {
if ( this._ffz_update_stats ) {
clearTimeout(this._ffz_update_stats);
this._ffz_update_stats = null;
}
// Stop scheduling this so it can die.
if ( this.isDestroyed )
return;
// Schedule an update.
if ( f.settings.player_stats )
this._ffz_update_stats = setTimeout(this.ffzUpdatePlayerStats.bind(this), 1000);
var channel_id = this.get("channel.id"),
container = this.get("element"),
player_cont = f.players && f.players[channel_id],
player, stats;
try {
player = player_cont && player_cont.get('player');
stats = player && player.getVideoInfo();
} catch(err) { } // This gets spammy if we try logging it.
2016-09-30 13:09:03 -04:00
if ( ! container || ! f.settings.player_stats || ! stats || ! stats.hls_latency_broadcaster )
return container && this.$("#ffz-player-stats").remove();
var el = container.querySelector("#ffz-player-stats");
if ( ! el ) {
var cont = container.querySelector('.cn-metabar__more');
if ( ! cont )
return;
var stat = utils.createElement('span'),
figure = utils.createElement('figure', 'icon cn-metabar__icon', constants.GRAPH + ' '),
balloon = utils.createElement('div', 'balloon balloon--tooltip balloon--up balloon--center'),
balloon_wrapper = utils.createElement('div', 'balloon-wrapper', figure);
el = utils.createElement('div', 'cn-metabar__ffz flex__item mg-l-1', balloon_wrapper);
balloon_wrapper.appendChild(stat);
balloon_wrapper.appendChild(balloon);
el.id = 'ffz-player-stats';
var viewers = cont.querySelector('#ffz-uptime-display') || cont.querySelector(".cn-metabar__livecount");
if ( viewers )
cont.insertBefore(el, viewers.nextSibling);
else
cont.appendChild(el);
}
var stat = el.querySelector('span'),
balloon = el.querySelector('.balloon');
var delay = Math.round(stats.hls_latency_broadcaster / 10) / 100,
dropped = utils.number_commas(stats.dropped_frames || 0),
bitrate;
if ( stats.playback_bytes_per_second )
bitrate = Math.round(stats.playback_bytes_per_second * 8 / 10.24) / 100;
else
bitrate = Math.round(stats.current_bitrate * 100) / 100;
var is_old = delay > 180;
if ( is_old ) {
delay = Math.floor(delay);
stat.textContent = utils.time_to_string(delay, true, delay > 172800) + ' old';
} else {
delay = delay.toString();
var ind = delay.indexOf('.');
delay += (ind === -1 ? '.00' : (ind >= delay.length - 2 ? '0' : '')) + 's';
stat.textContent = delay;
}
balloon.innerHTML = (is_old ? 'Video Information<br>' +
'Broadcast ' + utils.time_to_string(delay, true) + ' Ago<br><br>' : 'Stream Latency<br>') +
'Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p ' + stats.current_fps + ' fps<br>' +
'Playback Rate: ' + bitrate + ' Kbps<br>' +
'Dropped Frames: ' + dropped;
},
ffzUpdateChatters: function() {
var channel_id = this.get("channel.id"),
room = f.rooms && f.rooms[channel_id],
container = this.get('element');
if ( ! container || ! room || ! f.settings.chatter_count )
return container && this.$("#ffz-chatter-display").remove();
var chatter_count = Object.keys(room.room.get('ffz_chatters') || {}).length,
el = container.querySelector('#ffz-chatter-display span');
if ( ! el ) {
var cont = container.querySelector('.cn-metabar__more');
if ( ! cont )
return;
var stat = utils.createElement('span'),
figure = utils.createElement('figure', 'icon cn-metabar__icon', constants.ROOMS + ' '),
balloon = utils.createElement('div', 'balloon balloon--tooltip balloon--down balloon--center', 'Currently in Chat'),
balloon_wrapper = utils.createElement('div', 'balloon-wrapper', figure),
stat_wrapper = utils.createElement('div', 'cn-metabar__ffz flex__item mg-l-1', balloon_wrapper);
balloon_wrapper.appendChild(stat);
balloon_wrapper.appendChild(balloon);
stat_wrapper.id = 'ffz-chatter-display';
var viewers = cont.querySelector('#ffz-player-stats') || cont.querySelector('#ffz-uptime-display') || cont.querySelector(".cn-metabar__livecount") || cont.querySelector(".cn-metabar__viewcount");
if ( viewers )
cont.insertBefore(stat_wrapper, viewers.nextSibling);
else
cont.appendChild(stat_wrapper);
el = stat;
}
el.innerHTML = utils.number_commas(chatter_count);
}.observes('channel.id'),
ffzUpdateHostButton: function() {
var t = this,
channel_id = this.get("channel.id"),
hosted_id = this.get("channel.hostModeTarget.id"),
user = f.get_user(),
room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room,
now_hosting = room && room.ffz_host_target,
hosts_left = room && room.ffz_hosts_left,
el = this.get("element"),
update_button = function(channel, container, before) {
if ( ! container )
return;
var btn = container.querySelector('#ffz-ui-host-button');
if ( ! f.settings.stream_host_button || ! user || user.login === channel ) {
if ( btn )
btn.parentElement.removeChild(btn);
return;
}
if ( ! btn ) {
btn = utils.createElement('button', 'button button--hollow mg-l-1'),
btn.id = 'ffz-ui-host-button';
btn.addEventListener('click', t.ffzClickHost.bind(t, channel !== channel_id));
if ( before )
container.insertBefore(btn, before);
else
container.appendChild(btn);
jQuery(btn).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
btn.classList.remove('disabled');
btn.innerHTML = channel === now_hosting ? 'Unhost' : 'Host';
if ( now_hosting ) {
var name = FFZ.get_capitalization(now_hosting);
btn.title = 'You are now hosting ' + f.format_display_name(name, now_hosting, true)[0] + '.';
} else
btn.title = 'You are not hosting any channel.';
if ( typeof hosts_left === 'number' )
btn.title += ' You have ' + hosts_left + ' host command' + utils.pluralize(hosts_left) + ' remaining this half hour.';
};
if ( ! el )
return;
this.set("ffz_host_updating", false);
if ( channel_id ) {
var container = el.querySelector('.cn-metabar__more'),
share = container && container.querySelector('.js-share-box');
update_button(channel_id, container, share ? share.parentElement : null);
}
if ( hosted_id )
update_button(hosted_id, el.querySelector('.cn-hosting--bottom'));
}.observes('channel.id', 'channel.hostModeTarget.id'),
ffzClickHost: function(is_host, e) {
var btn = e.target,
target = this.get(is_host ? 'channel.hostModeTarget.id' : 'channel.id'),
user = f.get_user(),
room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room,
now_hosting = room && room.ffz_host_target;
if ( ! room || this.get('ffz_host_updating') )
return;
btn.classList.add('disabled');
btn.title = 'Updating...';
this.set('ffz_host_updating', true);
if ( now_hosting === target )
room.send('/unhost', true);
else
room.send('/host ' + target, true);
}
});
}
FFZ.prototype.modify_channel_redesign = function(view) {
var f = this;
utils.ember_reopen_view(view, {
ffz_init: function() {
// Twitch y u make me do dis
// (If this isn't the outer channel-redesign abort)
if ( this.parentView instanceof view )
return;
var channel_id = this.get("channel.id"),
el = this.get("element");
f._credesign = this;
this.ffzUpdateCoverHeight();
el.setAttribute('data-channel', channel_id);
el.classList.add('ffz-channel-container');
},
ffz_destroy: function() {
var channel_id = this.get("channel.id"),
el = this.get("element");
el.setAttribute('data-channel', '');
el.classList.remove('ffz-channel-container');
if ( f._credesign === this )
f._credesign = null;
},
ffzUpdateCoverHeight: function() {
var old_height = this.get('channelCoverHeight'),
setting = f.settings.hide_channel_banner,
banner_hidden = setting === 1 ? f.settings.channel_bar_bottom : setting > 0,
new_height = banner_hidden ? 0 : 380;
this.set('channelCoverHeight', new_height);
this.$("#channel").toggleClass('ffz-bar-fixed', this.get('isFixed'));
if ( old_height !== new_height )
this.scrollTo(this.$scrollContainer.scrollTop() + (new_height - old_height));
}.observes('isFixed')
2016-09-30 13:09:03 -04:00
})
}
FFZ.prototype.modify_channel_index = function(view) {
var f = this;
utils.ember_reopen_view(view, {
ffz_init: function() {
2016-09-30 13:09:03 -04:00
var channel_id = this.get('model.id'),
2015-07-04 17:06:36 -04:00
el = this.get('element');
2015-06-05 03:59:28 -04:00
f._cindex = this;
2016-09-30 13:09:03 -04:00
f.ws_send("sub", "channel." + channel_id);
2015-07-04 17:06:36 -04:00
2016-09-30 13:09:03 -04:00
el.setAttribute('data-channel', channel_id);
2015-07-04 17:06:36 -04:00
el.classList.add('ffz-channel');
2015-06-05 03:59:28 -04:00
this.ffzFixTitle();
this.ffzUpdateUptime();
this.ffzUpdateChatters();
this.ffzUpdateHostButton();
this.ffzUpdatePlayerStats();
2015-06-05 03:59:28 -04:00
// Listen to scrolling.
this._ffz_scroller = this.ffzOnScroll.bind(this);
jQuery(el).parents('.tse-scroll-content').on('scroll', this._ffz_scroller);
2015-07-04 17:06:36 -04:00
var views = this.get('element').querySelector('.svg-glyph_views:not(.ffz-svg)')
if ( views )
views.parentNode.classList.add('twitch-channel-views');
if ( f.settings.follow_buttons )
f.rebuild_following_ui();
if ( f.settings.srl_races )
f.rebuild_race_ui();
if ( f.settings.auto_theater ) {
2016-09-30 13:09:03 -04:00
var player = f.players && f.players[channel_id] && f.players[channel_id].get('player');
if ( player )
player.setTheatre(true);
}
this.$().on("click", ".ffz-creative-tag-link", function(e) {
if ( e.button !== 0 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey )
return;
utils.ember_lookup("router:main").transitionTo('creative.hashtag.index', this.getAttribute('data-tag'));
e.preventDefault();
return false;
});
2015-06-05 03:59:28 -04:00
},
ffz_destroy: function() {
2016-09-30 13:09:03 -04:00
var channel_id = this.get('model.id');
if ( channel_id )
f.ws_send("unsub", "channel." + channel_id);
this.get('element').setAttribute('data-channel', '');
2016-09-30 13:09:03 -04:00
if ( f._cindex === this )
f._cindex = null;
if ( this._ffz_update_uptime )
clearTimeout(this._ffz_update_uptime);
if ( this._ffz_update_stats )
clearTimeout(this._ffz_update_stats);
if ( this._ffz_scroller ) {
jQuery(this.get('element')).parents('.tse-scroll-content').off('scroll', this._ffz_scroller);
this._ffz_scroller = null;
}
document.body.classList.remove('ffz-small-player');
2016-09-30 13:09:03 -04:00
utils.update_css(f._channel_style, channel_id, null);
},
ffzOnScroll: function(event) {
// When we scroll past the bottom of the player, do stuff!
var top = event && event.target && event.target.scrollTop,
height = this.get('layout.playerSize.1');
if ( ! top )
top = jQuery(this.get('element')).parents('.tse-scroll-content').scrollTop();
document.body.classList.toggle('ffz-small-player', f.settings.small_player && top >= height);
},
2015-06-05 03:59:28 -04:00
ffzFixTitle: function() {
if ( f.has_bttv || ! f.settings.stream_title )
return;
2016-09-30 13:09:03 -04:00
var channel_id = this.get('model.id'),
status = this.get('model.status'),
game = this.get('model.game'),
2015-06-05 03:59:28 -04:00
2016-09-30 13:09:03 -04:00
tokens = f.tokenize_line(channel_id, channel_id, status, true);
2015-06-05 03:59:28 -04:00
if ( game === 'Creative' )
tokens = f.tokenize_ctags(tokens);
this.$("#broadcast-meta .title").html(f.render_tokens(tokens));
2016-09-30 13:09:03 -04:00
status = this.get('hostModeTarget.status');
channel_id = this.get('hostModeTarget.id');
game = this.get('hostModeTarget.game');
2016-09-30 13:09:03 -04:00
if ( channel_id ) {
tokens = f.tokenize_line(channel_id, channel_id, status, true);
if ( game === 'Creative' )
tokens = f.tokenize_ctags(tokens);
this.$(".target-meta .target-title").html(f.render_tokens(tokens));
}
2015-06-05 03:59:28 -04:00
},
ffzUpdateHostButton: function() {
2016-09-30 13:09:03 -04:00
var channel_id = this.get('model.id'),
hosted_id = this.get('hostModeTarget.id'),
user = f.get_user(),
room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room,
now_hosting = room && room.ffz_host_target,
hosts_left = room && room.ffz_hosts_left,
el = this.get('element');
this.set('ffz_host_updating', false);
if ( channel_id ) {
var container = el && el.querySelector('.stats-and-actions .channel-actions'),
btn = container && container.querySelector('#ffz-ui-host-button');
if ( ! container || ! f.settings.stream_host_button || ! user || user.login === channel_id ) {
if ( btn )
btn.parentElement.removeChild(btn);
} else {
if ( ! btn ) {
btn = document.createElement('span');
btn.id = 'ffz-ui-host-button';
btn.className = 'button button--text';
btn.addEventListener('click', this.ffzClickHost.bind(this, false));
var before;
try { before = container.querySelector(':scope > .theatre-button'); }
catch(err) { before = undefined; }
if ( before )
container.insertBefore(btn, before);
else
container.appendChild(btn);
jQuery(btn).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
btn.classList.remove('disabled');
btn.innerHTML = channel_id === now_hosting ? 'Unhost' : 'Host';
if ( now_hosting )
btn.title = 'You are now hosting ' + utils.sanitize(FFZ.get_capitalization(now_hosting)) + '.';
else
btn.title = 'You are not hosting any channel.';
if ( typeof hosts_left === "number" )
btn.title += ' You have ' + hosts_left + ' host command' + utils.pluralize(hosts_left) + ' remaining this half hour.';
}
}
if ( hosted_id ) {
var container = el && el.querySelector('#hostmode .channel-actions'),
btn = container && container.querySelector('#ffz-ui-host-button');
if ( ! container || ! f.settings.stream_host_button || ! user || user.login === hosted_id ) {
if ( btn )
btn.parentElement.removeChild(btn);
} else {
if ( ! btn ) {
btn = document.createElement('span');
btn.id = 'ffz-ui-host-button';
btn.className = 'button button--text';
btn.addEventListener('click', this.ffzClickHost.bind(this, true));
var before;
try { before = container.querySelector(':scope > .theatre-button'); }
catch(err) { before = undefined; }
if ( before )
container.insertBefore(btn, before);
else
container.appendChild(btn);
jQuery(btn).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
btn.classList.remove('disabled');
btn.innerHTML = hosted_id === now_hosting ? 'Unhost' : 'Host';
if ( now_hosting )
btn.title = 'You are currently hosting ' + utils.sanitize(FFZ.get_capitalization(now_hosting)) + '. Click to ' + (hosted_id === now_hosting ? 'unhost' : 'host') + ' this channel.';
else
btn.title = 'You are not currently hosting any channel. Click to host this channel.';
if ( typeof hosts_left === "number" )
btn.title += ' You have ' + hosts_left + ' host command' + utils.pluralize(hosts_left) + ' remaining this half hour.';
}
}
},
ffzClickHost: function(is_host, e) {
var btn = e.target,
2016-09-30 13:09:03 -04:00
target = is_host ? this.get('hostModeTarget.id') : this.get('model.id'),
user = f.get_user(),
room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room,
now_hosting = room && room.ffz_host_target;
if ( ! room || this.get('ffz_host_updating') )
return;
btn.classList.add('disabled');
btn.title = 'Updating...';
this.set('ffz_host_updating', true);
if ( now_hosting === target )
room.send("/unhost", true);
else
room.send("/host " + target, true);
},
2015-06-05 03:59:28 -04:00
ffzUpdateChatters: function() {
// Get the counts.
2016-09-30 13:09:03 -04:00
var room_id = this.get('model.id'),
2015-06-05 03:59:28 -04:00
room = f.rooms && f.rooms[room_id];
if ( ! room || ! f.settings.chatter_count ) {
var el = this.get('element').querySelector('#ffz-chatter-display');
el && el.parentElement.removeChild(el);
el = this.get('element').querySelector('#ffz-ffzchatter-display');
el && el.parentElement.removeChild(el);
return;
2015-06-05 03:59:28 -04:00
}
2015-06-05 03:59:28 -04:00
var chatter_count = Object.keys(room.room.get('ffz_chatters') || {}).length,
2015-07-04 17:06:36 -04:00
ffz_chatters = room.ffz_chatters || 0,
ffz_viewers = room.ffz_viewers || 0;
2015-06-05 03:59:28 -04:00
var el = this.get('element').querySelector('#ffz-chatter-display span');
if ( ! el ) {
var cont = this.get('element').querySelector('.stats-and-actions .channel-stats');
if ( ! cont )
return;
2015-06-05 03:59:28 -04:00
var stat = document.createElement('span');
stat.className = 'ffz stat';
stat.id = 'ffz-chatter-display';
2015-07-04 17:06:36 -04:00
stat.title = "Currently in Chat";
2015-06-05 03:59:28 -04:00
stat.innerHTML = constants.ROOMS + " ";
el = document.createElement("span");
stat.appendChild(el);
2015-06-05 03:59:28 -04:00
var other = cont.querySelector("#ffz-ffzchatter-display");
if ( other )
cont.insertBefore(stat, other);
else
cont.appendChild(stat);
jQuery(stat).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
2015-06-05 03:59:28 -04:00
}
2015-06-05 03:59:28 -04:00
el.innerHTML = utils.number_commas(chatter_count);
2015-07-04 17:06:36 -04:00
if ( ! ffz_chatters && ! ffz_viewers ) {
2015-06-05 03:59:28 -04:00
el = this.get('element').querySelector('#ffz-ffzchatter-display');
el && el.parentNode.removeChild(el);
return;
}
2015-06-05 03:59:28 -04:00
el = this.get('element').querySelector('#ffz-ffzchatter-display span');
if ( ! el ) {
var cont = this.get('element').querySelector('.stats-and-actions .channel-stats');
if ( ! cont )
return;
2015-06-05 03:59:28 -04:00
var stat = document.createElement('span');
stat.className = 'ffz stat';
stat.id = 'ffz-ffzchatter-display';
2015-07-04 17:06:36 -04:00
stat.title = "Viewers (In Chat) with FrankerFaceZ";
2015-06-05 03:59:28 -04:00
stat.innerHTML = constants.ZREKNARF + " ";
el = document.createElement("span");
stat.appendChild(el);
2015-06-05 03:59:28 -04:00
var other = cont.querySelector("#ffz-chatter-display");
if ( other )
cont.insertBefore(stat, other.nextSibling);
else
cont.appendChild(stat);
jQuery(stat).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
2015-06-05 03:59:28 -04:00
}
2015-07-04 17:06:36 -04:00
el.innerHTML = utils.number_commas(ffz_viewers) + " (" + utils.number_commas(ffz_chatters) + ")";
2015-06-05 03:59:28 -04:00
},
ffzUpdatePlayerStats: function() {
if ( this._ffz_update_stats ) {
clearTimeout(this._ffz_update_stats);
this._ffz_update_stats = null;
}
// Schedule an update.
if ( f.settings.player_stats )
this._ffz_update_stats = setTimeout(this.ffzUpdatePlayerStats.bind(this), 1000);
2016-09-30 13:09:03 -04:00
var channel_id = this.get('model.id'),
hosted_id = this.get('hostModeTarget.id'),
el = this.get('element');
if ( channel_id ) {
var container = el && el.querySelector('.stats-and-actions .channel-stats'),
stat_el = container && container.querySelector('#ffz-ui-player-stats'),
el = stat_el && stat_el.querySelector('span'),
je,
player_cont = f.players && f.players[channel_id],
player = undefined, stats = undefined;
try {
player = player_cont && player_cont.get && player_cont.get('player');
stats = player && player.getVideoInfo();
} catch(err) {
f.error("Channel ffzUpdatePlayerStats: player.getVideoInfo: " + err);
}
if ( ! container || ! f.settings.player_stats || ! stats || ! stats.hls_latency_broadcaster || Number.isNaN(stats.hls_latency_broadcaster) ) {
if ( stat_el )
stat_el.parentElement.removeChild(stat_el);
} else {
if ( ! stat_el ) {
stat_el = document.createElement('span');
stat_el.id = 'ffz-ui-player-stats';
stat_el.className = 'ffz stat';
stat_el.innerHTML = constants.GRAPH + " ";
el = document.createElement('span');
stat_el.appendChild(el);
var other = container.querySelector('#ffz-uptime-display');
if ( other )
container.insertBefore(stat_el, other.nextSibling);
else
container.appendChild(stat_el);
je = jQuery(stat_el);
je.hover(
function() { je.data("hover", true).tipsy("show") },
function() { je.data("hover", false).tipsy("hide") })
.data("hover", false)
.tipsy({trigger: 'manual', html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
} else
je = jQuery(stat_el);
var delay = Math.round(stats.hls_latency_broadcaster / 10) / 100,
dropped = utils.number_commas(stats.dropped_frames || 0),
bitrate;
if ( stats.playback_bytes_per_second )
bitrate = Math.round(stats.playback_bytes_per_second * 8 / 10.24) / 100;
else
bitrate = Math.round(stats.current_bitrate * 100) / 100;
if ( delay > 180 ) {
delay = Math.floor(delay);
stat_el.setAttribute('original-title', 'Video Information<br>Broadcast ' + utils.time_to_string(delay, true) + ' Ago<br><br>Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p @ ' + stats.current_fps + '<br>Playback Rate: ' + bitrate + ' Kbps<br>Dropped Frames: ' + dropped);
el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old';
} else {
stat_el.setAttribute('original-title', 'Stream Latency<br>Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p @ ' + stats.current_fps + '<br>Playback Rate: ' + bitrate + ' Kbps<br>Dropped Frames: ' + dropped);
delay = delay.toString();
var ind = delay.indexOf('.');
if ( ind === -1 )
delay = delay + '.00';
else if ( ind >= delay.length - 2 )
delay = delay + '0';
el.textContent = delay + 's';
}
if ( je.data("hover") )
je.tipsy("hide").tipsy("show");
}
}
if ( hosted_id ) {
var container = el && el.querySelector('#hostmode .channel-stats'),
stat_el = container && container.querySelector('#ffz-ui-player-stats'),
el = stat_el && stat_el.querySelector('span'),
je,
player_cont = f.players && f.players[hosted_id],
player = undefined, stats = undefined;
try {
player = player_cont && player_cont.ffz_player;
stats = player && player.getVideoInfo();
} catch(err) {
f.error("Channel ffzUpdatePlayerStats: player.getVideoInfo: " + err);
}
if ( ! container || ! f.settings.player_stats || ! stats || ! stats.hls_latency_broadcaster || Number.isNaN(stats.hls_latency_broadcaster) ) {
if ( stat_el )
stat_el.parentElement.removeChild(stat_el);
} else {
if ( ! stat_el ) {
stat_el = document.createElement('span');
stat_el.id = 'ffz-ui-player-stats';
stat_el.className = 'ffz stat';
stat_el.innerHTML = constants.GRAPH + " ";
el = document.createElement('span');
stat_el.appendChild(el);
var other = container.querySelector('#ffz-uptime-display');
if ( other )
container.insertBefore(stat_el, other.nextSibling);
else
container.appendChild(stat_el);
je = jQuery(stat_el);
je.hover(
function() { je.data("hover", true).tipsy("show") },
function() { je.data("hover", false).tipsy("hide") })
.data("hover", false)
.tipsy({trigger: 'manual', html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
} else
je = jQuery(stat_el);
var delay = Math.round(stats.hls_latency_broadcaster / 10) / 100,
dropped = utils.number_commas(stats.dropped_frames || 0),
bitrate;
if ( stats.playback_bytes_per_second )
bitrate = Math.round(stats.playback_bytes_per_second * 8 / 10.24) / 100;
else
bitrate = Math.round(stats.current_bitrate * 100) / 100;
if ( delay > 180 ) {
delay = Math.floor(delay);
stat_el.setAttribute('original-title', 'Video Information<br>Broadcast ' + utils.time_to_string(delay, true) + ' Ago<br><br>Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p @ ' + stats.current_fps + '<br>Playback Rate: ' + bitrate + ' Kbps<br>Dropped Frames: ' + dropped);
el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old';
} else {
stat_el.setAttribute('original-title', 'Stream Latency<br>Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p @ ' + stats.current_fps + '<br>Playback Rate: ' + bitrate + ' Kbps<br>Dropped Frames: ' + dropped);
delay = delay.toString();
var ind = delay.indexOf('.');
if ( ind === -1 )
delay = delay + '.00';
else if ( ind >= delay.length - 2 )
delay = delay + '0';
el.textContent = delay + 's';
}
if ( je.data("hover") )
je.tipsy("hide").tipsy("show");
}
}
},
2015-06-05 03:59:28 -04:00
ffzUpdateUptime: function() {
if ( this._ffz_update_uptime ) {
clearTimeout(this._ffz_update_uptime);
delete this._ffz_update_uptime;
}
2016-09-30 13:09:03 -04:00
var controller = utils.ember_lookup('controller:channel');
if ( ! f.settings.stream_uptime || ! (controller && controller.get('isLiveAccordingToKraken')) ) {
2015-06-05 03:59:28 -04:00
var el = this.get('element').querySelector('#ffz-uptime-display');
if ( el )
el.parentElement.removeChild(el);
return;
}
2015-06-05 03:59:28 -04:00
// Schedule an update.
this._ffz_update_uptime = setTimeout(this.ffzUpdateUptime.bind(this), 1000);
// Determine when the channel last went live.
2016-09-30 13:09:03 -04:00
var online = this.get("model.stream.created_at"),
now = Date.now() - (f._ws_server_offset || 0);
online = online && utils.parse_date(online);
2015-06-05 03:59:28 -04:00
var uptime = online && Math.floor((now - online.getTime()) / 1000) || -1;
if ( uptime < 0 ) {
var el = this.get('element').querySelector('#ffz-uptime-display');
if ( el )
el.parentElement.removeChild(el);
2015-06-05 03:59:28 -04:00
return;
}
2015-06-05 03:59:28 -04:00
var el = this.get('element').querySelector('#ffz-uptime-display span');
if ( ! el ) {
var cont = this.get('element').querySelector('.stats-and-actions .channel-stats');
if ( ! cont )
return;
var stat = document.createElement('span');
stat.className = 'ffz stat';
stat.id = 'ffz-uptime-display';
stat.title = "Stream Uptime <nobr>(since " + online.toLocaleString() + ")</nobr>";
stat.innerHTML = constants.CLOCK + " ";
el = document.createElement("span");
stat.appendChild(el);
var viewers = cont.querySelector(".live-count");
if ( viewers )
cont.insertBefore(stat, viewers.nextSibling);
else {
try {
viewers = cont.querySelector("script:nth-child(0n+2)");
cont.insertBefore(stat, viewers.nextSibling);
} catch(err) {
cont.insertBefore(stat, cont.childNodes[0]);
}
}
jQuery(stat).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
2015-06-05 03:59:28 -04:00
}
el.innerHTML = utils.time_to_string(uptime, false, false, false, f.settings.stream_uptime === 1 || f.settings.stream_uptime === 3);
}
2015-06-05 03:59:28 -04:00
});
}
2015-06-05 03:59:28 -04:00
// ---------------
// Settings
// ---------------
FFZ.settings_info.auto_theater = {
type: "boolean",
value: false,
category: "Appearance",
no_mobile: true,
no_bttv: true,
name: "Automatic Theater Mode",
help: "Automatically enter theater mode when opening a channel."
};
FFZ.settings_info.small_player = {
type: "boolean",
value: false,
no_mobile: true,
no_bttv: true,
category: "Appearance",
name: "Mini-Player on Scroll",
help: "When you scroll down on the page, shrink the player and put it in the upper right corner so you can still watch.",
on_update: function(val) {
if ( ! val )
return document.body.classList.remove('ffz-small-player');
else if ( this._vodc )
this._vodc.ffzOnScroll();
else if ( this._cindex )
this._cindex.ffzOnScroll();
}
}
2015-06-05 03:59:28 -04:00
FFZ.settings_info.chatter_count = {
type: "boolean",
value: false,
no_mobile: true,
2015-06-05 03:59:28 -04:00
category: "Channel Metadata",
name: "Chatter Count",
help: "Display the current number of users connected to chat beneath the channel.",
on_update: function(val) {
if ( this._cindex )
this._cindex.ffzUpdateChatters();
if ( ! val || ! this.rooms )
return;
// Refresh the data.
for(var room_id in this.rooms)
this.rooms.hasOwnProperty(room_id) && this.rooms[room_id].room && this.rooms[room_id].room.ffzInitChatterCount();
}
};
FFZ.settings_info.channel_views = {
type: "boolean",
value: true,
no_mobile: true,
2015-06-05 03:59:28 -04:00
category: "Channel Metadata",
name: "Channel Views",
help: 'Display the number of times the channel has been viewed beneath the stream.',
on_update: function(val) {
document.body.classList.toggle("ffz-hide-view-count", !val);
}
};
FFZ.settings_info.hosted_channels = {
type: "boolean",
value: true,
no_mobile: true,
category: "Channel Metadata",
name: "Channel Hosting",
help: "Display other channels that have been featured by the current channel.",
on_update: function(val) {
var cb = document.querySelector('input.ffz-setting-hosted-channels');
if ( cb )
cb.checked = val;
var Chat = utils.ember_lookup('controller:chat'),
room = Chat && Chat.get('currentChannelRoom');
if ( room )
room.setHostMode({
hostTarget: room.ffz_host_target,
recentlyJoined: true
});
}
};
FFZ.settings_info.stream_host_button = {
type: "boolean",
value: true,
no_mobile: true,
category: "Channel Metadata",
name: "Host This Channel Button",
help: "Display a button underneath streams that make it easy to host them with your own channel.",
on_update: function(val) {
if ( this._cindex )
this._cindex.ffzUpdateHostButton();
}
};
2015-06-05 03:59:28 -04:00
FFZ.settings_info.stream_uptime = {
type: "select",
options: {
0: "Disabled",
1: "Enabled",
2: "Enabled (with Seconds)",
3: "Enabled (Channel Only)",
4: "Enabled (Channel Only with Seconds)"
},
value: 1,
process_value: function(val) {
if ( val === false )
return 0;
if ( val === true )
return 2;
if ( typeof val === "string" )
return parseInt(val || "0") || 0;
return val;
},
2015-06-05 03:59:28 -04:00
no_mobile: true,
2015-06-05 03:59:28 -04:00
category: "Channel Metadata",
name: "Stream Uptime",
help: 'Display the stream uptime under a channel by the viewer count.',
on_update: function(val) {
if ( this._cindex )
this._cindex.ffzUpdateUptime();
}
};
FFZ.settings_info.stream_title = {
type: "boolean",
value: true,
no_bttv: true,
no_mobile: true,
2015-06-05 03:59:28 -04:00
category: "Channel Metadata",
name: "Title Links",
help: "Make links in stream titles clickable.",
on_update: function(val) {
if ( this._cindex )
this._cindex.ffzFixTitle();
}
};
2016-09-30 13:09:03 -04:00
FFZ.settings_info.channel_bar_bottom = {
type: "boolean",
value: false,
no_bttv: true,
no_mobile: true,
category: "Appearance",
name: "Channel Bar on Bottom",
help: "Hide the profile banner and position the channel bar at the bottom of the screen.",
on_update: function(val) {
if ( this.has_bttv )
return;
var banner_hidden = this.settings.hide_channel_banner;
banner_hidden = banner_hidden === 1 ? val : banner_hidden > 0;
2016-09-30 13:09:03 -04:00
utils.toggle_cls('ffz-channel-bar-bottom')(val);
utils.toggle_cls('ffz-hide-channel-banner')(banner_hidden);
if ( this._credesign )
this._credesign.ffzUpdateCoverHeight();
2016-09-30 13:09:03 -04:00
var Layout = utils.ember_lookup('service:layout');
if ( Layout )
Ember.propertyDidChange(Layout, 'windowHeight');
}
}
FFZ.settings_info.hide_channel_banner = {
type: "select",
options: {
0: "Never",
1: "When Channel Bar is on Bottom",
2: "Always"
},
value: 1,
process_value: utils.process_int(1),
no_bttv: true,
no_mobile: true,
category: "Appearance",
name: "Hide Channel Banner",
help: "Hide the banner at the top of channel pages.",
on_update: function(val) {
if ( this.has_bttv )
return;
var is_hidden = val === 1 ? this.settings.channel_bar_bottom : val > 0;
utils.toggle_cls('ffz-hide-channel-banner')(is_hidden);
if ( this._credesign )
this._credesign.ffzUpdateCoverHeight();
var Layout = utils.ember_lookup('service:layout');
if ( Layout )
Ember.propertyDidChange(Layout, 'windowHeight');
}
}
2016-09-30 13:09:03 -04:00
FFZ.settings_info.channel_bar_collapse = {
type: "boolean",
value: false,
no_bttv: true,
no_mobile: true,
category: "Appearance",
name: "Minimal Channel Bar",
help: "Slide the channel bar mostly out of view when it's not being used.",
on_update: function(val) {
if ( this.has_bttv )
return;
utils.toggle_cls('ffz-minimal-channel-bar')(val);
var Layout = utils.ember_lookup('service:layout');
if ( Layout )
Ember.propertyDidChange(Layout, 'windowHeight');
}
}
FFZ.settings_info.channel_title_top = {
type: "select",
options: {
0: "Disabled",
1: "On Top",
2: "On Top, Minimal"
},
value: 0,
process_value: function(val) {
if ( typeof val === "string" ) {
val = parseInt(val);
if ( isNaN(val) || ! isFinite(val) )
val = 0;
}
return val;
},
no_bttv: true,
no_mobile: true,
category: "Appearance",
name: "Channel Title on Top",
help: "Display the channel title and game above the player rather than below.",
on_update: function(val) {
if ( this.has_bttv )
return;
document.body.classList.toggle('ffz-minimal-channel-title', val === 2);
document.body.classList.toggle('ffz-channel-title-top', val > 0);
var Layout = utils.ember_lookup('service:layout');
if ( Layout )
Ember.propertyDidChange(Layout, 'windowHeight');
}
}
FFZ.settings_info.theater_stats = {
type: "boolean",
value: true,
no_mobile: true,
category: "Channel Metadata",
name: "Display on Theater Mode Hover",
help: "Show the channel metadata and actions over the video player in theater mode when you hover it with your mouse.",
on_update: function(val) {
document.body.classList.toggle('ffz-theater-stats', val);
}
};
FFZ.basic_settings.channel_info = {
type: "select",
options: {
0: "Disabled",
1: "Enabled",
2: "Enabled (with Seconds)",
3: "Enabled (Channel Only)",
4: "Enabled (Channel Only with Seconds)"
},
category: "General",
name: "Stream Uptime",
help: "Display the current stream's uptime under the player.",
get: function() {
return this.settings.stream_uptime;
},
set: function(val) {
if ( typeof val === 'string' )
val = parseInt(val || "0");
this.settings.set('stream_uptime', val);
}
}