<commit_before>var RedditTV = Class.extend({
	init: function() {
		self = this;

		self.Globals = $.extend({}, {
			/* Current URL for AJAX, etc */
			current_url: window.location.origin + window.location.pathname,

			/* build uri for search type channels */
			search_str: (function () {
				var one_day = 86400,
					date = new Date(),
					unixtime_ms = date.getTime(),
					unixtime = parseInt(unixtime_ms / 1000);
				return "search/.json?q=%28and+%28or+site%3A%27youtube.com%27+site%3A%27vimeo.com%27+site%3A%27youtu.be%27%29+timestamp%3A"+(unixtime - 5*one_day)+"..%29&restrict_sr=on&sort=top&syntax=cloudsearch";
			})(),

			/* Channels Object */
			channels: [
				/*{channel: 'All', type: 'search', feed: '/r/all/'},
				{channel: 'Videos', type: 'normal', feed: '/r/videos/'},*/
				],

			/* Video Domains */
			domains: [
				'5min.com', 'abcnews.go.com', 'animal.discovery.com', 'animoto.com', 'atom.com',
				'bambuser.com', 'bigthink.com', 'blip.tv', 'break.com',
				'cbsnews.com', 'cnbc.com', 'cnn.com', 'colbertnation.com', 'collegehumor.com',
				'comedycentral.com', 'crackle.com', 'dailymotion.com', 'dsc.discovery.com', 'discovery.com',
				'dotsub.com', 'edition.cnn.com', 'escapistmagazine.com', 'espn.go.com',
				'fancast.com', 'flickr.com', 'fora.tv', 'foxsports.com',
				'funnyordie.com', 'gametrailers.com', 'godtube.com', 'howcast.com', 'hulu.com',
				'justin.tv', 'kinomap.com', 'koldcast.tv', 'liveleak.com', 'livestream.com',
				'mediamatters.org', 'metacafe.com', 'money.cnn.com',
				'movies.yahoo.com', 'msnbc.com', 'nfb.ca', 'nzonscreen.com',
				'overstream.net', 'photobucket.com', 'qik.com', 'redux.com',
				'revision3.com', 'revver.com', 'schooltube.com',
				'screencast.com', 'screenr.com', 'sendables.jibjab.com',
				'spike.com', 'teachertube.com', 'techcrunch.tv', 'ted.com',
				'thedailyshow.com', 'theonion.com', 'traileraddict.com', 'trailerspy.com',
				'trutv.com', 'twitvid.com', 'ustream.com', 'viddler.com', 'video.google.com',
				'video.nationalgeographic.com', 'video.pbs.org', 'video.yahoo.com', 'vids.myspace.com', 'vimeo.com',
				'wordpress.tv', 'worldstarhiphop.com', 'xtranormal.com',
				'youtube.com', 'youtu.be', 'zapiks.com'
				],

			sorting: 'hot',

			videos: [],
			user_channels: [],
			channel_sorting: [],
			video_minimum: 10,
			cur_video: 0,
			cur_chan: {},
			cur_chan_req: null,
			cur_vid_req: null,
			current_anchor: '',
			auto: true,
			sfw: true,
			shuffle: false,
			shuffled: [],
			theme: 'light',
			videoListMouse: false,

			content_minwidth: 130,  // minimum width of #content w/o width of player
			content_minheight: 320, // minimum height of #content w/o height of player
			vd_minwidth: 30,		// minimum width of #video-display w/o width of player
			vd_minheight: 213,	  // minimum height of #video-display w/o height of player

			ads: {}
		}, Globals); // end Globals

		// Load ads
		self.Globals.videos['/sponsor'] = { 'video': [] };
		self.apiCall('ads', null, function(data) {
			self.Globals.ads = data;
			self.Globals.videos['/sponsor'].video = self.formatAdVideos(data.videos);
			$(document).trigger('adsLoaded');
		});

		// Load sponsored channel
		if(self.Globals.sponsored_channels.length >= 1){
			self.Globals.sponsored_channels = self.formatSponsoredChannels(self.Globals.sponsored_channels);
			self.Globals.channels = self.Globals.channels.concat(self.Globals.sponsored_channels);
			for(c in self.Globals.sponsored_channels){
				self.Globals.videos[self.Globals.sponsored_channels[c].feed] = {
					video: self.Globals.sponsored_channels[c].videos
				};
			}
		}

		$('#video-list').hide();

		self.loadSettings();
		self.displayChannels();
		self.setBindings();

		if (!self.Globals.current_anchor) {
			var firstChannel = $('#channels a.channel:not(#add-channel-button):first');
			if (firstChannel.length) self.loadChannel(self.getChanObj(firstChannel.data('feed')), null);
		}
	}, // init()

	loadSettings: function() {
		var channels_cookie = $.jStorage.get('user_channels'),
			auto_cookie = $.jStorage.get('auto'),
			sfw_cookie = $.jStorage.get('sfw'),
			theme_cookie = $.jStorage.get('theme'),
			shuffle_cookie = $.jStorage.get('shuffle'),
			sorting_cookie = $.jStorage.get('channel_sorting');

		if(auto_cookie !== null && auto_cookie !== self.Globals.auto){
			self.Globals.auto = (auto_cookie === 'true') ? true : false;
		}
		if(shuffle_cookie !== null && shuffle_cookie !== self.Globals.shuffle){
			self.Globals.shuffle = (shuffle_cookie === 'true') ? true : false;
		}
		if(sfw_cookie !== null && sfw_cookie !== self.Globals.sfw){
			self.Globals.sfw = (sfw_cookie === 'true') ? true : false;
		}
		$('#sorting a[href="#sort=' + self.Globals.sorting + '"]').addClass('active');

		// Mark settings as active
		var settingNames = ['auto', 'shuffle', 'sfw'];
		settingNames.forEach(function(i) {
			if (self.Globals[i])
				$('#settings .settings-' + i).addClass('active').find('input').attr('checked', true);
		});

		if (sorting_cookie !== null && sorting_cookie !== self.Globals.channel_sorting) {
			self.Globals.channel_sorting = sorting_cookie;
		}

		if(theme_cookie !== null && theme_cookie !== self.Globals.theme){
			self.Globals.theme = theme_cookie;
		}
		if(channels_cookie !== null && channels_cookie !== self.Globals.user_channels){
			self.Globals.user_channels = channels_cookie;
			self.Globals.channels = self.Globals.user_channels.concat(self.Globals.channels);
		}
	}, // loadSettings()

	setBindings: function() {
		var $filloverlay = $('#fill-overlay'), $fillnav = $('#fill-nav');
		$filloverlay.mouseenter(function() {
			$fillnav.slideDown('slow');
		});
		$filloverlay.mouseleave(function() {
			$fillnav.slideUp('slow');
		});
		$fillnav.click(function(){
			fillScreen();
		});
		$('#css li a').click(function() {
			loadTheme($(this).attr('rel'));
			return false;
		});
		$('#settings .settings-auto input').change(function() {
			self.Globals.auto = ($(this).is(':checked')) ? true : false;
			$.jStorage.set('auto', self.Globals.auto);
		});
		$('#settings .settings-shuffle input').change(function() {
			self.Globals.shuffle = ($(this).is(':checked')) ? true : false;
			self.Globals.shuffled = []; //reset
			$.jStorage.set('shuffle', self.Globals.shuffle);
		});
		$('#settings .settings-sfw input').change(function() {
			self.Globals.sfw = ($(this).is(':checked')) ? true : false;
			if(!self.Globals.sfw){
				if(!confirm("Are you over 18?")){
					$(this).removeClass('active').find('input').prop("checked", true);
					self.Globals.sfw = true;
				}
			}
			$.jStorage.set('sfw', self.Globals.sfw);
			self.showHideNsfwThumbs(self.Globals.sfw);
		});
		$('#settings .settings-fill').click(function() {
			fillScreen();
		});
		$('#settings #hax a').click(function() {
			window.open($(this).attr('href'));
		});
		$('#next-button').click(function() {
			self.loadVideo('next');
		});
		$('#prev-button').click(function() {
			self.loadVideo('prev');
		});
		$('#video-list').bind('mousewheel', function(event,delta){
			// $(this).animate({ scrollLeft: this.scrollLeft - (delta * 30) }, 100);
			this.scrollLeft -= (delta * 30);
			event.preventDefault();
		});
		$('#sorting a').click(function() {
			if ($(this).hasClass('active')) return false;
			
			$('#sorting').removeClass('open')
				.find('a').removeClass('active');
			$(this).addClass('active');
			
			self.Globals.sorting = $(this).attr('href').replace(/^.*#sort=/, '')
			self.Globals.videos = [];
			loadChannel(self.Globals.channels[self.Globals.cur_chan], null);

			return false;
		});

		$(document).keydown(function (e) {
			if (e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) return true;

			if(!$(e.target).is('form>*, input')) {
				var keyCode = e.keyCode || e.which, arrow = {left: 37, up: 38, right: 39, down: 40 };
				switch (keyCode) {
				case arrow.left:  case 72: // h
					rtv.loadVideo('prev');
					break;
				case arrow.up:	case 75: // k
					rtv.chgChan('up');
					break;
				case arrow.right: case 76: // l
					rtv.loadVideo('next');
					break;
				case arrow.down:  case 74: // j
					rtv.chgChan('down');
					break;
				case 32:
					rtv.togglePlay();
					break;
				case 70:
					$('#fill').attr('checked', true);
					rtv.fillScreen();
					break;
				case 27:
					if($('#fill').is(':checked')){
						rtv.fillScreen();
					}
					break;
				case 67:
					window.open($('#video-title>a').attr('href'), '_blank');
					break;
				}
				return false;
			}
		});

		/*$(window).resize(function() {
			self.resizePlayer();
		});*/

		/* Anchor Checker */
		if("onhashchange" in window){
			self.checkAnchor(); //perform initial check if hotlinked
			window.onhashchange = function(){
				self.checkAnchor();
			};
		}else{
			setInterval(self.checkAnchor, 100);
		}

		// Video thumbnail onClicks
		$('#video-list').on(
			'click',
			'.thumbnail',
			function() {
				// Kinda busted?
				$(this).addClass('focus');
				self.closeVideoList();
				self.Globals.videoListMouse = false;

				if ( $(this).hasClass('sponsored') ) {
					self.loadVideo(parseInt($(this).attr('data-adNum')), true);
				}
			}
		);

		// Channel thumbnail onClicks
		$('#channels').on(
			'click',
			'a.channel',
			function() {
				$('#channels a.channel').removeClass('focus');
				$(this).addClass('focus');
			}
		);

		// Channel thumbnail delete onClicks
		$('#channels').on(
			'click',
			'a.channel span.delete',
			function() {
				var thumb  = $(this).parent(),
				    anchor = $(this).parents('a.channel'),
				    confirm;

				anchor.addClass('deleting');
				confirm = $('<div class="delete confirm">Are you sure you want to delete this channel?<button type="button" class="btn btn-primary" title="Delete">YES</button></div>');
				thumb.append(confirm);
				confirm.hide().fadeIn(100);

				return false;
			}
		);

		// Channel thumbnail delete bindings
		$('#channels').on(
			'click',
			'a.channel.deleting button',
			function() {
				var anchor    = $(this).parents('a.channel'),
				    feed      = anchor.data('feed'),
				    deleted   = self.removeChannel(feed),
				    isCurChan = (self.Globals.cur_chan.feed == feed);

				if (deleted && isCurChan) {
					anchor
						.addClass('temp')
						.removeClass('deleting')
						.find('.thumbnail span.delete')
							.attr({
								'class': 'add',
								'title': 'Add this channel permanently'
							})
							.text('+');

					anchor.find('.thumbnail .confirm').fadeOut(100, function() {
						$(this).remove();
					});
				}

				if (deleted && !isCurChan) {
					anchor.transition({
						scale   : 0,
						opacity : 0
					}, 300, function() {
						$(this).remove();

						self.bindChannelSorting(true);
					});
				}

				self.saveChannelOrder();

				return false;
			}
		);

		// Remove deletion confirmation on mouseleave
		$('#channels').on(
			'mouseleave',
			'a.channel.deleting',
			function() {
				var thumb  = $(this).find('.thumbnail');

				$(this).removeClass('deleting');
				$(this).find('.thumbnail .confirm').fadeOut(100, function() {
					$(this).remove();
				});
			}
		);

		// Channel thumbnail delete onClicks
		$('#channels').on(
			'click',
			'a.channel span.add',
			function() {
				var thumb  = $(this).parent(),
				    anchor = $(this).parents('a.channel'),
				    feed   = anchor.data('feed');

				self.addChannel(feed);

				return false;
			}
		);

		// VidList tooltips
		$('#video-list').on(
			'mouseenter mouseleave',
			'.thumbnail',
			function(e) {
				if (e.type == 'mouseenter') {
					if ( !$(this).attr('title') || $('#video-list').hasClass('scrolling')) return; // Don't show tooltips while scrolling

					var toolTip = $('#vid-list-tooltip'),
						toolTipPos = 0,
						title = $(this).attr('title'),
						toolTipCss = { 'z-index': 9001, 'left': '', 'right': '' };

					$(this).data('title', $(this).attr('title'));
					$(this).attr('title', '');
					toolTip.show().html(title);
					toolTipPos = $(this).offset().left;
					toolTipCss.left = (toolTipPos < 0) ? 0 : toolTipPos;
					if ($(document).width() - toolTipPos <= 150) {
						toolTipCss.left = '';
						toolTipCss.right = 0;
					}
					toolTip.css(toolTipCss);
				} else if (e.type == 'mouseleave') {
					$(this).attr('title', $(this).data('title'));
					$('#vid-list-tooltip').hide();
				}
			}
		);

		$('#add-channel-button, #video-return').click(self.toggleAddChannel);

		$('#add-channel form').on('submit', self.addChannelFromForm);

		$('#toggle-settings').click(function() {
			consoleLog('toggling settings');
			$('#settings').toggleClass('open');
			return;

			var div = $('#settings');
			if (div.hasClass('open')) {
				$('#settings').removeClass('open');
				// $('#settings').fadeIn();
			} else {
				$('#settings *:not(input):hidden').fadeIn();
				// $('#settings').addClass('open');
			}
		});

		$('#add-channel input.channel-name').on('keyup', function() {
			self.addChannelName();

			// Only continue if the value has changed, no arrow keys, etc
			if ( $(this).data('val') == $(this).val() ) return;
			$(this).data('val', $(this).val());

			$('#add-channel-message').text('');
			$('#add-channel').addClass('disabled');

			window.clearTimeout(self.Globals.addChannelCheck);
			self.Globals.addChannelCheck = window.setTimeout(self.addChannelCheck, 500);
		});

		$.each(self.Globals.recommended_channels, function(i, channel) {
			var anchor, thumb, name;

			if (i >= 8) return; // Only display the first 8

			anchor = $('<a />')
				.addClass('grid-25 channel')
				.attr({
					href : '#',
					'data-feed' : channel.feed
				})
				.appendTo('#add-channel .recommended.channels');

			thumb = $('<div class="thumbnail" />')
						.css({
							'background-image' : 'url(' + channel.thumbnail + ')'
						})
						.appendTo(anchor);

			name = $('<span class="name" />')
						.text(channel.channel)
						.appendTo(anchor);

			anchor.on('click', function() {
				$('#add-channel input.channel-name')
					.val( $(this).attr('data-feed').replace(/^\/\w+\//, '') )
					.focus();

				$('#add-channel .recommended.channels').fadeOut(200);
				$('#add-channel .channel-to-add .channel').remove();
				self.addChannelName();
				self.addChannelCheck();

				return false;
			});
		});

		$('header').mouseenter(function(){
			// console.log('enter header');
			self.Globals.videoListMouse = true;
			setTimeout(self.videoListOpenTimeout, 500);
		});
		$('#settings').mouseenter(function(){
			// console.log('enter settings')
			self.Globals.videoListMouse = false;
		});
		$('header').mouseleave(function(){
			// console.log('exit header')
			self.Globals.videoListMouse = false;
			setTimeout(self.videoListCloseTimeout, 1000);
		});
	}, // setBindings()

	bindChannelSorting: function(animate) {
		var channels = $('#channels');
		
		if (animate) channels.addClass('animate');

		if ( !$('#channels[class*="shapeshifted_container"]').length )
			channels.on('ss-arranged', self.saveChannelOrder);

		channels.shapeshift({
			selector: 'a.channel',
			ignore: '#add-channel-button, .sponsor',
			align: 'center',
			colWidth: 250,
			columns: 4,
			minColumns: 4,
			gutterX: 0,
			gutterY: 0,
			paddingX: 0,
			paddingY: 0,
		});

		if (animate) channels.removeClass('animate');
	}, // bindChannelSorting()

	saveChannelOrder: function() {
		var feeds = [];
		$('#channels a.channel:not(#add-channel-button):not(.sponsor):not(.temp)').each(function() {
			feeds.push($(this).data('feed'));
		});

		self.Globals.channel_sorting = feeds;

		if (self.Globals.channels.length > feeds.length) {
			$.each(self.Globals.channels, function(i, chan) {
				if (chan.owner != 'sponsor' && self.Globals.channel_sorting.indexOf(chan.feed) == -1)
					self.Globals.channel_sorting.push(chan.feed);
			});
		}

		$.jStorage.set('channel_sorting', self.Globals.channel_sorting);
	}, // saveChannelOrder()

	displayChannels: function() {
		var $channel_list = $('#channel-list'),
			$list = $('<ul></ul>'),
			$channel_base = $('#channels a.channel:first'),
			sorted = (self.Globals.channel_sorting.length);

		$.each(self.Globals.channels, function(i, chan) {
			if (chan.owner == 'sponsor' && chan.feed) self.displayChannel(chan);
		});

		var channels = (sorted) ? self.Globals.channel_sorting : self.Globals.channels;
		$.each(channels, function(i, chan) {
			chan = (sorted) ? self.getChanObj(chan) : chan;
			if (chan.owner != 'sponsor' && chan.feed) self.displayChannel(chan);
		});

    	self.bindChannelSorting();
	}, // displayChannels()

	displayChannel: function(chan, added) {
		var title, display_title, class_str='', remove_str='',
			$channel_base = $('#add-channel-button'),
			$channel = $channel_base.clone().removeAttr('id');

		// data = data[0];
		// chan.feed = data.feed;
		chan_title = chan.feed.split("/");
		chan_title = "/"+chan_title[1]+"/"+chan_title[2];

		/*chan = 0
		for(chan=0; chan<Globals.channels.length; chan++){
			console.log(Globals.channels[chan].feed)
			if(Globals.channels[chan].feed == data.feed)
				break;
		}
		console.log(chan);*/

		if ( !chan.channel ) chan.channel = chan_title.replace(/^.*\//, '');

		display_title = chan.channel.length > 20 ?
			chan.channel.replace(/[aeiou]/gi,'').substr(0,20) :
			chan.channel;

		/*if(isUserChan(Globals.channels[chan].channel)){
			class_str = 'class="user-chan"';
			remove_str = '<a id="remove-'+chan+'" class="remove-chan">-</a>';
		}*/

		var chanAttr = {
				// id: 'channel-' + chan,
				href: '#' + chan.feed,
				title: chan_title,
				'data-feed' : chan.feed
			};

		if (chan.owner) {
			var ownerClass = chan.owner;
			if (chan.owner == 'temp') ownerClass += ' user';
			$channel.addClass(ownerClass);

			chanAttr['data-owner'] = chan.owner;

			var spanHtml = {
				'user' : '<span class="delete" title="Delete this channel">&times;</span>',
			    'temp' : '<span class="add" title="Add this channel permanently">+</span>'
			};
	
			if (spanHtml[chan.owner])
				$channel.find('.thumbnail')
					.append(spanHtml[chan.owner]);
		}

		$channel
			.show()
			.attr(chanAttr)
			.find('.name')
				.html(display_title);
			// .removeClass('loading') // temp

		if (added || chan.owner == 'sponsor') {
			$channel.insertAfter( $('#channels a.sponsor').or('#add-channel-button') );
		} else {
			$channel.appendTo('#channels');
		}

		if (chan.thumbnail) {
			$channel.find('.thumbnail')
				.css({
					'background-image': 'url(' + chan.thumbnail + ')'
				});
		}

		if ( !chan.thumbnail || chan.owner == 'user' ) { // get the newest thumbnail if it's a user channel
			self.apiCall(
				'channel_thumbnail',
				{ 'feed' : chan.feed },
				function(data) {
					var channel   = data[0],
						thumb	  = channel.thumbnail_url,
						chanIndex = self.getChan(channel.feed),
						chanObj   = self.Globals.channels[chanIndex];

					if (!thumb || thumb == '') return;

					if (chanObj) {
						chanObj.thumbnail = thumb;
						if (chanObj.owner == 'user') {
							self.Globals.user_channels[chanIndex] = chanObj;
							$.jStorage.set('user_channels', self.Globals.user_channels);
						}
					}
					$('#channels a.channel[data-feed="' + channel.feed + '"]').find('.thumbnail')
						.css({
							'background-image': 'url(' + thumb + ')'
						});

				}
			);
		}

		if (added) {
			self.bindChannelSorting(true);
			$channel.css({
				scale   : 0,
				opacity : 0
			})
			.delay(300)
			.transition({
				scale   : 1,
				opacity : 1
			}, 300);
		}

		return $channel;

		// $('#channel>ul').prepend('<li id="channel-'+chan+'" title="'+title+'" '+class_str+'><img src="http://i2.ytimg.com/vi/NUkwaiJgDGY/hqdefault.jpg" />'+display_title+remove_str+'</li>');

		/*$('#remove-'+chan).bind(
			'click',
			{channel: chan},
			function(event) {
				removeChan(event.data.channel);
			}
		);*/

	}, // displayChannel()

	loadChannel: function(channel, video_id) {
		// console.log('[loadChannel]', channel, video_id);
		var $video_embed = $('#video-embed'),
			$video_title = $('#video-title'),
			this_chan, title, getChan, anchor;

		getChan = self.getChanObj(channel.feed);
		this_chan = (getChan) ? getChan : channel;

		anchor = $('#channels a.channel[data-feed="' + this_chan.feed + '"]');
		if ( !anchor.length ) {
			this_chan.owner = 'temp';
			anchor = self.displayChannel(this_chan, true);
		}
		anchor.addClass('focus');

		self.Globals.shuffled = [];
		self.Globals.cur_chan = this_chan;
		
		/*$('#video-list').stop(true, true).animate({ height:0, padding:0 }, 500, function() {
			$(this).empty().hide();
		});*/
		$('#prev-button,#next-button').css({ 'visibility':'hidden', 'display':'none' });
		$('#vote-button').empty();
		$('#video-source').empty();

		title = channel.feed.split("/");
		title = "/"+title[1]+"/"+title[2];

		$video_title.html('Loading '+title+' ...');
		var thumbId = video_id;
		if (!thumbId) {
			var vidFeed = self.Globals.videos[channel.feed];
			if (vidFeed && vidFeed.video && vidFeed.video.length) thumbId = vidFeed.video[0].id;
		}
		var loadingThumb = (!thumbId && this_chan.thumbnail) ? this_chan.thumbnail : self.getThumbnailUrl(channel, thumbId);
		self.loadingAnimation(title, loadingThumb);
		$video_embed.empty();
		
		// TODO: Change to highlight the channel in the grid instead
		// $('#channel-list>ul>li').removeClass('chan-selected');
		// $('#channel-'+this_chan).addClass('chan-selected');

		var npTitle = self.Globals.cur_chan.feed;
		if (self.Globals.cur_chan.channel) npTitle = self.Globals.cur_chan.channel + ' - ' + npTitle;
		$('#now-playing-title').empty().append(npTitle+" &#9660;");

		
		if(self.Globals.videos[this_chan.feed] === undefined){
			self.redditApiCall('videos', { 'channel': this_chan, 'video_id': video_id },
				function(data, local) {
					var this_chan = local.channel,
					    video_id  = local.video_id;

					if (self.Globals.videos[this_chan.feed].video.length > 0) {
						if (video_id !== null) {
							self.loadVideoById(video_id);
						} else {
							self.loadVideoList(this_chan);
							self.Globals.cur_video = 0;
							self.loadVideo('first');
						}
					}
				},
				function(jXHR, textStatus, errorThrown, local) {
					self.tvError('Error loading channel.');
				});
		}else{
			if(self.Globals.videos[this_chan.feed].video.length > 0){
				if(video_id !== null){
					self.loadVideoById(video_id);
				}else{
					self.loadVideoList(this_chan);
					self.Globals.cur_video = 0;
					self.loadVideo('first');
				}
			}
		}
	}, // loadChannel()

	getFeedURI: function(channel_obj) {
		var sorting    = self.Globals.sorting.split(':'),
		    sortType   = '',
		    sortOption = '',
		    extras     = '',
		    limit      = 100,
		    uri;

		if (channel_obj.video_count && channel_obj.video_count < self.Globals.video_minimum) {
			limit = 1000;
			if (channel_obj.last_id) extras = '&after=' + channel_obj.last_id;
		}

		if (sorting.length === 2) {

			sortType = sorting[0] + '/';
			sortOption = '&t=' + sorting[1];
		}

		if (channel_obj.type === 'search' && sorting.length === 1) {

			uri = channel_obj.feed + Globals.search_str + '&limit=' + limit;
		}
		else {

			uri = channel_obj.feed + sortType + '.json?limit=' + limit + sortOption;
			// Can we do this with searching? sortType seems in the way.
			// uri = channel_obj.feed + sortType."/search/.json?q=%28and+%28or+site%3A%27youtube.com%27+site%3A%27vimeo.com%27+site%3A%27youtu.be%27%29+timestamp%3A1382227035..%29&restrict_sr=on&sort=top&syntax=cloudsearch&limit=100";

		}

		uri += extras;

		console.log(uri);
		return uri;
	}, // getFeedURI()

	isVideo: function(video_domain) {
		return (self.Globals.domains.indexOf(video_domain) !== -1);
	},

	isEmpty: function(obj) {
		for(var prop in obj) {
			if(obj.hasOwnProperty(prop)){
				return false;
			}
		}
		return true;
	}, // isEmpty()

	filterVideoDupes: function(arr) {
		var i, out=[], obj={}, original_length = arr.length;
		
		//work from last video to first video (so hottest dupe is left standing)
		//first pass on media embed
		for (i=arr.length-1; i>=0; i--) {
			if(typeof obj[arr[i].media_embed.content] !== 'undefined'){
				delete obj[arr[i].media_embed.content];
			}
			obj[arr[i].media_embed.content]=arr[i];
		}
		for (i in obj) {
			out.push(obj[i]);
		}

		arr = out.reverse();
		out = [];
		obj = {};

		//second pass on url
		for (i=arr.length-1; i>=0; i--) {
			if(typeof obj[arr[i].url] !== 'undefined'){
				delete obj[arr[i].url];
			}
			obj[arr[i].url]=arr[i];
		}
		for (i in obj) {
			out.push(obj[i]);
		}

		return out.reverse();
	}, // filterVideoDupes()

	findVideoById: function(id,chan) {
		for(var x in self.Globals.videos[chan].video){
			if(self.Globals.videos[chan].video[x].id === id){
				return Number(x); //if found return array pos
			}
		}
		return false; //not found
	}, // findVideoById()

	sfwCheck: function(video, chan) {
		return (self.Globals.sfw && self.Globals.videos[chan].video[video].over_18);
	},

	showHideNsfwThumbs: function(sfw) {
		$('.nsfw_thumb').toggleClass('visible', !sfw);
	},

	getThumbnailUrl: function(chan, video_id) {
		var video;
		if (typeof chan == 'object') {
			if (!chan.video) return false;
 			video = chan.video[video_id];
		} else {
			if (!self.Globals.videos[chan].video) return false;
			video = self.Globals.videos[chan].video[video_id];
		}

		if (video.media.oembed) {
			return video.media.oembed.thumbnail_url !== undefined ? 
				video.media.oembed.thumbnail_url :
				'img/noimage.png';
		}
		else {
			return 'img/noimage.png';
		}
	}, // getThumbnailUrl()

	createEmbed: function(url, type){
		switch(type){
		case 'youtube.com': case 'youtu.be':
			return youtube.createEmbed(url);
		case 'vimeo.com':
			return vimeo.createEmbed(url);
		default:
			return false;
		}
	}, // createEmbed()

	prepEmbed: function(embed, type) {
		// Flash and z-index on Windows fix
		if (!embed.match(/wmode/))
			embed = embed
				.replace(/<embed /, '<embed wmode="opaque" ')
				.replace(/<\/object>/, '<param name="wmode" value="opaque" /></object>');

		switch(type){
		case 'youtube.com': case 'youtu.be':
			return youtube.prepEmbed(embed);
		case 'vimeo.com':
			return vimeo.prepEmbed(embed);
		case 'size':
			embed = embed.replace(/height\="(\d\w+)"/gi, 'height="100%"');
			embed = embed.replace(/width\="(\d\w+)"/gi, 'width="100%"');
		}
		
		return embed;
	}, // prepEmbed()

	addListeners: function(type) {
		switch (type) {
			case 'vimeo.com':
				vimeo.addListeners();
		}
	}, // addListeners()

	apiCall: function(action, data, successCallback, errorCallback) {
		var apiData = $.extend({ 'action' : action }, (typeof data != 'object') ? {} : data );

		if ( !$.isFunction(successCallback) ) successCallback = function(){};
		if ( !$.isFunction(errorCallback) )	errorCallback	= function(){};

		$.ajax({
			url: self.Globals.current_url + 'db/api.php',
			data: apiData,
			dataType: 'json',
			success: successCallback,
			error: function(jXHR, textStatus, errorThrown) {
				console.log('[apiCall]', action);
				console.log('[ERROR]', textStatus);
				console.log('[ERROR]', errorThrown);
				errorCallback(jXHR, textStatus, errorThrown);
			}
		});
	}, // apiCall()

	redditApiCall: function(action, data, successCallback, errorCallback) {
		if ( !$.isFunction(successCallback) ) successCallback = function(){};
		if ( !$.isFunction(errorCallback) )	errorCallback	= function(){};

		var last_req = self.Globals.cur_chan_req;
		if (last_req !== null) last_req.abort();

		var redditApiError = function(jXHR, textStatus, errorThrown, local) {
			if(textStatus !== 'abort') {
				// alert('Could not load feed. Is reddit down?');
			}

			console.log(jXHR, textStatus, errorThrown);
			errorCallback(jXHR, textStatus, errorThrown, local);

			$('body').removeClass('video-loading');
		}

		if (action == 'videos') {
			var video_id  = data.video_id,
			    this_chan = data.channel;

			self.Globals.cur_chan_req = $.ajax({
				url: 'http://www.reddit.com' + self.getFeedURI(this_chan),
				dataType: 'jsonp',
				jsonp: 'jsonp',
				timeout: 5000, // Server doesn't return JSON on error, have to rely on this.
				context: { 'action' : action, 'data' : data, 'successCallback' : successCallback, 'errorCallback' : errorCallback },
				success: function(data, textStatus) {
					var this_chan  = this.data.channel,
					    chan_index = self.getChan(this_chan.feed);

					//clear out stored videos if not grabbing more videos
					if ( !this_chan.last_id || !self.Globals.videos[this_chan.feed] ) {
						self.Globals.videos[this_chan.feed] = {};
						self.Globals.videos[this_chan.feed].video = [];
					}

					for (var x in data.data.children) {
						if ( !this_chan.channel ) this_chan.channel = data.data.children[x].data.subreddit;

						if (self.isVideo(data.data.children[x].data.domain) && (data.data.children[x].data.score > 1)) {
							if ( self.isEmpty(data.data.children[x].data.media_embed) || data.data.children[x].data.domain === 'youtube.com' || data.data.children[x].data.domain === 'youtu.be' ){
								var created = self.createEmbed(data.data.children[x].data.url, data.data.children[x].data.domain);
								if (created !== false) {
									data.data.children[x].data.media_embed.content = created.embed;
									data.data.children[x].data.media = {};
									data.data.children[x].data.media.oembed = {};
									data.data.children[x].data.media.oembed.thumbnail_url = created.thumbnail;
								}
							}
							if (data.data.children[x].data.media_embed.content) {
								self.Globals.videos[this_chan.feed].video.push(data.data.children[x].data);
							}
						}

						this_chan.last_id = data.data.children[x].data.name;
					}

					//remove duplicates
					self.Globals.videos[this_chan.feed].video = self.filterVideoDupes(self.Globals.videos[this_chan.feed].video);
					this.data.videos = self.Globals.videos[this_chan.feed].video;
					this.data.data = data;

					if (!this.data.videos.length) return redditApiError(null, textStatus, null, this.data);

					if (this.data.videos.length < self.Globals.video_minimum) {
						this_chan.page = (this_chan.page) ? this_chan.page + 1 : 2;
						this_chan.video_count = this.data.videos.length;

						if (this_chan.page > 1 && this_chan.video_count == 0) {
							self.tvError('No videos found in ' + this_chan.channel);
						} else {
							self.redditApiCall(this.action, this.data, this.successCallback, this.errorCallback);
						}

						var moreVideos = true;
					}

					self.Globals.channels[self.getChan(this_chan.feed)] = this_chan;

					if (moreVideos) return;

					successCallback(data, this.data);

					$('body').removeClass('video-loading');<commit_msg>Globals.current_url fix for IE, disable infinite loop if no more posts exist, allow for at least 5 "pages" of 1000 posts<commit_after>var RedditTV = Class.extend({
	init: function() {
		self = this;

		self.Globals = $.extend({}, {
			/* Current URL for AJAX, etc */
			current_url: window.location.protocol + '//' + window.location.host + window.location.pathname,

			/* build uri for search type channels */
			search_str: (function () {
				var one_day = 86400,
					date = new Date(),
					unixtime_ms = date.getTime(),
					unixtime = parseInt(unixtime_ms / 1000);
				return "search/.json?q=%28and+%28or+site%3A%27youtube.com%27+site%3A%27vimeo.com%27+site%3A%27youtu.be%27%29+timestamp%3A"+(unixtime - 5*one_day)+"..%29&restrict_sr=on&sort=top&syntax=cloudsearch";
			})(),

			/* Channels Object */
			channels: [
				/*{channel: 'All', type: 'search', feed: '/r/all/'},
				{channel: 'Videos', type: 'normal', feed: '/r/videos/'},*/
				],

			/* Video Domains */
			domains: [
				'5min.com', 'abcnews.go.com', 'animal.discovery.com', 'animoto.com', 'atom.com',
				'bambuser.com', 'bigthink.com', 'blip.tv', 'break.com',
				'cbsnews.com', 'cnbc.com', 'cnn.com', 'colbertnation.com', 'collegehumor.com',
				'comedycentral.com', 'crackle.com', 'dailymotion.com', 'dsc.discovery.com', 'discovery.com',
				'dotsub.com', 'edition.cnn.com', 'escapistmagazine.com', 'espn.go.com',
				'fancast.com', 'flickr.com', 'fora.tv', 'foxsports.com',
				'funnyordie.com', 'gametrailers.com', 'godtube.com', 'howcast.com', 'hulu.com',
				'justin.tv', 'kinomap.com', 'koldcast.tv', 'liveleak.com', 'livestream.com',
				'mediamatters.org', 'metacafe.com', 'money.cnn.com',
				'movies.yahoo.com', 'msnbc.com', 'nfb.ca', 'nzonscreen.com',
				'overstream.net', 'photobucket.com', 'qik.com', 'redux.com',
				'revision3.com', 'revver.com', 'schooltube.com',
				'screencast.com', 'screenr.com', 'sendables.jibjab.com',
				'spike.com', 'teachertube.com', 'techcrunch.tv', 'ted.com',
				'thedailyshow.com', 'theonion.com', 'traileraddict.com', 'trailerspy.com',
				'trutv.com', 'twitvid.com', 'ustream.com', 'viddler.com', 'video.google.com',
				'video.nationalgeographic.com', 'video.pbs.org', 'video.yahoo.com', 'vids.myspace.com', 'vimeo.com',
				'wordpress.tv', 'worldstarhiphop.com', 'xtranormal.com',
				'youtube.com', 'youtu.be', 'zapiks.com'
				],

			sorting: 'hot',

			videos: [],
			user_channels: [],
			channel_sorting: [],
			video_minimum: 10,
			cur_video: 0,
			cur_chan: {},
			cur_chan_req: null,
			cur_vid_req: null,
			current_anchor: '',
			auto: true,
			sfw: true,
			shuffle: false,
			shuffled: [],
			theme: 'light',
			videoListMouse: false,

			content_minwidth: 130,  // minimum width of #content w/o width of player
			content_minheight: 320, // minimum height of #content w/o height of player
			vd_minwidth: 30,		// minimum width of #video-display w/o width of player
			vd_minheight: 213,	  // minimum height of #video-display w/o height of player

			ads: {}
		}, Globals); // end Globals

		// Load ads
		self.Globals.videos['/sponsor'] = { 'video': [] };
		self.apiCall('ads', null, function(data) {
			self.Globals.ads = data;
			self.Globals.videos['/sponsor'].video = self.formatAdVideos(data.videos);
			$(document).trigger('adsLoaded');
		});

		// Load sponsored channel
		if(self.Globals.sponsored_channels.length >= 1){
			self.Globals.sponsored_channels = self.formatSponsoredChannels(self.Globals.sponsored_channels);
			self.Globals.channels = self.Globals.channels.concat(self.Globals.sponsored_channels);
			for(c in self.Globals.sponsored_channels){
				self.Globals.videos[self.Globals.sponsored_channels[c].feed] = {
					video: self.Globals.sponsored_channels[c].videos
				};
			}
		}

		$('#video-list').hide();

		self.loadSettings();
		self.displayChannels();
		self.setBindings();

		if (!self.Globals.current_anchor) {
			var firstChannel = $('#channels a.channel:not(#add-channel-button):first');
			if (firstChannel.length) self.loadChannel(self.getChanObj(firstChannel.data('feed')), null);
		}
	}, // init()

	loadSettings: function() {
		var channels_cookie = $.jStorage.get('user_channels'),
			auto_cookie = $.jStorage.get('auto'),
			sfw_cookie = $.jStorage.get('sfw'),
			theme_cookie = $.jStorage.get('theme'),
			shuffle_cookie = $.jStorage.get('shuffle'),
			sorting_cookie = $.jStorage.get('channel_sorting');

		if(auto_cookie !== null && auto_cookie !== self.Globals.auto){
			self.Globals.auto = (auto_cookie === 'true') ? true : false;
		}
		if(shuffle_cookie !== null && shuffle_cookie !== self.Globals.shuffle){
			self.Globals.shuffle = (shuffle_cookie === 'true') ? true : false;
		}
		if(sfw_cookie !== null && sfw_cookie !== self.Globals.sfw){
			self.Globals.sfw = (sfw_cookie === 'true') ? true : false;
		}
		$('#sorting a[href="#sort=' + self.Globals.sorting + '"]').addClass('active');

		// Mark settings as active
		var settingNames = ['auto', 'shuffle', 'sfw'];
		settingNames.forEach(function(i) {
			if (self.Globals[i])
				$('#settings .settings-' + i).addClass('active').find('input').attr('checked', true);
		});

		if (sorting_cookie !== null && sorting_cookie !== self.Globals.channel_sorting) {
			self.Globals.channel_sorting = sorting_cookie;
		}

		if(theme_cookie !== null && theme_cookie !== self.Globals.theme){
			self.Globals.theme = theme_cookie;
		}
		if(channels_cookie !== null && channels_cookie !== self.Globals.user_channels){
			self.Globals.user_channels = channels_cookie;
			self.Globals.channels = self.Globals.user_channels.concat(self.Globals.channels);
		}
	}, // loadSettings()

	setBindings: function() {
		var $filloverlay = $('#fill-overlay'), $fillnav = $('#fill-nav');
		$filloverlay.mouseenter(function() {
			$fillnav.slideDown('slow');
		});
		$filloverlay.mouseleave(function() {
			$fillnav.slideUp('slow');
		});
		$fillnav.click(function(){
			fillScreen();
		});
		$('#css li a').click(function() {
			loadTheme($(this).attr('rel'));
			return false;
		});
		$('#settings .settings-auto input').change(function() {
			self.Globals.auto = ($(this).is(':checked')) ? true : false;
			$.jStorage.set('auto', self.Globals.auto);
		});
		$('#settings .settings-shuffle input').change(function() {
			self.Globals.shuffle = ($(this).is(':checked')) ? true : false;
			self.Globals.shuffled = []; //reset
			$.jStorage.set('shuffle', self.Globals.shuffle);
		});
		$('#settings .settings-sfw input').change(function() {
			self.Globals.sfw = ($(this).is(':checked')) ? true : false;
			if(!self.Globals.sfw){
				if(!confirm("Are you over 18?")){
					$(this).removeClass('active').find('input').prop("checked", true);
					self.Globals.sfw = true;
				}
			}
			$.jStorage.set('sfw', self.Globals.sfw);
			self.showHideNsfwThumbs(self.Globals.sfw);
		});
		$('#settings .settings-fill').click(function() {
			fillScreen();
		});
		$('#settings #hax a').click(function() {
			window.open($(this).attr('href'));
		});
		$('#next-button').click(function() {
			self.loadVideo('next');
		});
		$('#prev-button').click(function() {
			self.loadVideo('prev');
		});
		$('#video-list').bind('mousewheel', function(event,delta){
			// $(this).animate({ scrollLeft: this.scrollLeft - (delta * 30) }, 100);
			this.scrollLeft -= (delta * 30);
			event.preventDefault();
		});
		$('#sorting a').click(function() {
			if ($(this).hasClass('active')) return false;
			
			$('#sorting').removeClass('open')
				.find('a').removeClass('active');
			$(this).addClass('active');
			
			self.Globals.sorting = $(this).attr('href').replace(/^.*#sort=/, '')
			self.Globals.videos = [];
			loadChannel(self.Globals.channels[self.Globals.cur_chan], null);

			return false;
		});

		$(document).keydown(function (e) {
			if (e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) return true;

			if(!$(e.target).is('form>*, input')) {
				var keyCode = e.keyCode || e.which, arrow = {left: 37, up: 38, right: 39, down: 40 };
				switch (keyCode) {
				case arrow.left:  case 72: // h
					rtv.loadVideo('prev');
					break;
				case arrow.up:	case 75: // k
					rtv.chgChan('up');
					break;
				case arrow.right: case 76: // l
					rtv.loadVideo('next');
					break;
				case arrow.down:  case 74: // j
					rtv.chgChan('down');
					break;
				case 32:
					rtv.togglePlay();
					break;
				case 70:
					$('#fill').attr('checked', true);
					rtv.fillScreen();
					break;
				case 27:
					if($('#fill').is(':checked')){
						rtv.fillScreen();
					}
					break;
				case 67:
					window.open($('#video-title>a').attr('href'), '_blank');
					break;
				}
				return false;
			}
		});

		/*$(window).resize(function() {
			self.resizePlayer();
		});*/

		/* Anchor Checker */
		if("onhashchange" in window){
			self.checkAnchor(); //perform initial check if hotlinked
			window.onhashchange = function(){
				self.checkAnchor();
			};
		}else{
			setInterval(self.checkAnchor, 100);
		}

		// Video thumbnail onClicks
		$('#video-list').on(
			'click',
			'.thumbnail',
			function() {
				// Kinda busted?
				$(this).addClass('focus');
				self.closeVideoList();
				self.Globals.videoListMouse = false;

				if ( $(this).hasClass('sponsored') ) {
					self.loadVideo(parseInt($(this).attr('data-adNum')), true);
				}
			}
		);

		// Channel thumbnail onClicks
		$('#channels').on(
			'click',
			'a.channel',
			function() {
				$('#channels a.channel').removeClass('focus');
				$(this).addClass('focus');
			}
		);

		// Channel thumbnail delete onClicks
		$('#channels').on(
			'click',
			'a.channel span.delete',
			function() {
				var thumb  = $(this).parent(),
				    anchor = $(this).parents('a.channel'),
				    confirm;

				anchor.addClass('deleting');
				confirm = $('<div class="delete confirm">Are you sure you want to delete this channel?<button type="button" class="btn btn-primary" title="Delete">YES</button></div>');
				thumb.append(confirm);
				confirm.hide().fadeIn(100);

				return false;
			}
		);

		// Channel thumbnail delete bindings
		$('#channels').on(
			'click',
			'a.channel.deleting button',
			function() {
				var anchor    = $(this).parents('a.channel'),
				    feed      = anchor.data('feed'),
				    deleted   = self.removeChannel(feed),
				    isCurChan = (self.Globals.cur_chan.feed == feed);

				if (deleted && isCurChan) {
					anchor
						.addClass('temp')
						.removeClass('deleting')
						.find('.thumbnail span.delete')
							.attr({
								'class': 'add',
								'title': 'Add this channel permanently'
							})
							.text('+');

					anchor.find('.thumbnail .confirm').fadeOut(100, function() {
						$(this).remove();
					});
				}

				if (deleted && !isCurChan) {
					anchor.transition({
						scale   : 0,
						opacity : 0
					}, 300, function() {
						$(this).remove();

						self.bindChannelSorting(true);
					});
				}

				self.saveChannelOrder();

				return false;
			}
		);

		// Remove deletion confirmation on mouseleave
		$('#channels').on(
			'mouseleave',
			'a.channel.deleting',
			function() {
				var thumb  = $(this).find('.thumbnail');

				$(this).removeClass('deleting');
				$(this).find('.thumbnail .confirm').fadeOut(100, function() {
					$(this).remove();
				});
			}
		);

		// Channel thumbnail delete onClicks
		$('#channels').on(
			'click',
			'a.channel span.add',
			function() {
				var thumb  = $(this).parent(),
				    anchor = $(this).parents('a.channel'),
				    feed   = anchor.data('feed');

				self.addChannel(feed);

				return false;
			}
		);

		// VidList tooltips
		$('#video-list').on(
			'mouseenter mouseleave',
			'.thumbnail',
			function(e) {
				if (e.type == 'mouseenter') {
					if ( !$(this).attr('title') || $('#video-list').hasClass('scrolling')) return; // Don't show tooltips while scrolling

					var toolTip = $('#vid-list-tooltip'),
						toolTipPos = 0,
						title = $(this).attr('title'),
						toolTipCss = { 'z-index': 9001, 'left': '', 'right': '' };

					$(this).data('title', $(this).attr('title'));
					$(this).attr('title', '');
					toolTip.show().html(title);
					toolTipPos = $(this).offset().left;
					toolTipCss.left = (toolTipPos < 0) ? 0 : toolTipPos;
					if ($(document).width() - toolTipPos <= 150) {
						toolTipCss.left = '';
						toolTipCss.right = 0;
					}
					toolTip.css(toolTipCss);
				} else if (e.type == 'mouseleave') {
					$(this).attr('title', $(this).data('title'));
					$('#vid-list-tooltip').hide();
				}
			}
		);

		$('#add-channel-button, #video-return').click(self.toggleAddChannel);

		$('#add-channel form').on('submit', self.addChannelFromForm);

		$('#toggle-settings').click(function() {
			consoleLog('toggling settings');
			$('#settings').toggleClass('open');
			return;

			var div = $('#settings');
			if (div.hasClass('open')) {
				$('#settings').removeClass('open');
				// $('#settings').fadeIn();
			} else {
				$('#settings *:not(input):hidden').fadeIn();
				// $('#settings').addClass('open');
			}
		});

		$('#add-channel input.channel-name').on('keyup', function() {
			self.addChannelName();

			// Only continue if the value has changed, no arrow keys, etc
			if ( $(this).data('val') == $(this).val() ) return;
			$(this).data('val', $(this).val());

			$('#add-channel-message').text('');
			$('#add-channel').addClass('disabled');

			window.clearTimeout(self.Globals.addChannelCheck);
			self.Globals.addChannelCheck = window.setTimeout(self.addChannelCheck, 500);
		});

		$.each(self.Globals.recommended_channels, function(i, channel) {
			var anchor, thumb, name;

			if (i >= 8) return; // Only display the first 8

			anchor = $('<a />')
				.addClass('grid-25 channel')
				.attr({
					href : '#',
					'data-feed' : channel.feed
				})
				.appendTo('#add-channel .recommended.channels');

			thumb = $('<div class="thumbnail" />')
						.css({
							'background-image' : 'url(' + channel.thumbnail + ')'
						})
						.appendTo(anchor);

			name = $('<span class="name" />')
						.text(channel.channel)
						.appendTo(anchor);

			anchor.on('click', function() {
				$('#add-channel input.channel-name')
					.val( $(this).attr('data-feed').replace(/^\/\w+\//, '') )
					.focus();

				$('#add-channel .recommended.channels').fadeOut(200);
				$('#add-channel .channel-to-add .channel').remove();
				self.addChannelName();
				self.addChannelCheck();

				return false;
			});
		});

		$('header').mouseenter(function(){
			// console.log('enter header');
			self.Globals.videoListMouse = true;
			setTimeout(self.videoListOpenTimeout, 500);
		});
		$('#settings').mouseenter(function(){
			// console.log('enter settings')
			self.Globals.videoListMouse = false;
		});
		$('header').mouseleave(function(){
			// console.log('exit header')
			self.Globals.videoListMouse = false;
			setTimeout(self.videoListCloseTimeout, 1000);
		});
	}, // setBindings()

	bindChannelSorting: function(animate) {
		var channels = $('#channels');
		
		if (animate) channels.addClass('animate');

		if ( !$('#channels[class*="shapeshifted_container"]').length )
			channels.on('ss-arranged', self.saveChannelOrder);

		channels.shapeshift({
			selector: 'a.channel',
			ignore: '#add-channel-button, .sponsor',
			align: 'center',
			colWidth: 250,
			columns: 4,
			minColumns: 4,
			gutterX: 0,
			gutterY: 0,
			paddingX: 0,
			paddingY: 0,
		});

		if (animate) channels.removeClass('animate');
	}, // bindChannelSorting()

	saveChannelOrder: function() {
		var feeds = [];
		$('#channels a.channel:not(#add-channel-button):not(.sponsor):not(.temp)').each(function() {
			feeds.push($(this).data('feed'));
		});

		self.Globals.channel_sorting = feeds;

		if (self.Globals.channels.length > feeds.length) {
			$.each(self.Globals.channels, function(i, chan) {
				if (chan.owner != 'sponsor' && self.Globals.channel_sorting.indexOf(chan.feed) == -1)
					self.Globals.channel_sorting.push(chan.feed);
			});
		}

		$.jStorage.set('channel_sorting', self.Globals.channel_sorting);
	}, // saveChannelOrder()

	displayChannels: function() {
		var $channel_list = $('#channel-list'),
			$list = $('<ul></ul>'),
			$channel_base = $('#channels a.channel:first'),
			sorted = (self.Globals.channel_sorting.length);

		$.each(self.Globals.channels, function(i, chan) {
			if (chan.owner == 'sponsor' && chan.feed) self.displayChannel(chan);
		});

		var channels = (sorted) ? self.Globals.channel_sorting : self.Globals.channels;
		$.each(channels, function(i, chan) {
			chan = (sorted) ? self.getChanObj(chan) : chan;
			if (chan.owner != 'sponsor' && chan.feed) self.displayChannel(chan);
		});

    	self.bindChannelSorting();
	}, // displayChannels()

	displayChannel: function(chan, added) {
		var title, display_title, class_str='', remove_str='',
			$channel_base = $('#add-channel-button'),
			$channel = $channel_base.clone().removeAttr('id');

		// data = data[0];
		// chan.feed = data.feed;
		chan_title = chan.feed.split("/");
		chan_title = "/"+chan_title[1]+"/"+chan_title[2];

		/*chan = 0
		for(chan=0; chan<Globals.channels.length; chan++){
			console.log(Globals.channels[chan].feed)
			if(Globals.channels[chan].feed == data.feed)
				break;
		}
		console.log(chan);*/

		if ( !chan.channel ) chan.channel = chan_title.replace(/^.*\//, '');

		display_title = chan.channel.length > 20 ?
			chan.channel.replace(/[aeiou]/gi,'').substr(0,20) :
			chan.channel;

		/*if(isUserChan(Globals.channels[chan].channel)){
			class_str = 'class="user-chan"';
			remove_str = '<a id="remove-'+chan+'" class="remove-chan">-</a>';
		}*/

		var chanAttr = {
				// id: 'channel-' + chan,
				href: '#' + chan.feed,
				title: chan_title,
				'data-feed' : chan.feed
			};

		if (chan.owner) {
			var ownerClass = chan.owner;
			if (chan.owner == 'temp') ownerClass += ' user';
			$channel.addClass(ownerClass);

			chanAttr['data-owner'] = chan.owner;

			var spanHtml = {
				'user' : '<span class="delete" title="Delete this channel">&times;</span>',
			    'temp' : '<span class="add" title="Add this channel permanently">+</span>'
			};
	
			if (spanHtml[chan.owner])
				$channel.find('.thumbnail')
					.append(spanHtml[chan.owner]);
		}

		$channel
			.show()
			.attr(chanAttr)
			.find('.name')
				.html(display_title);
			// .removeClass('loading') // temp

		if (added || chan.owner == 'sponsor') {
			$channel.insertAfter( $('#channels a.sponsor').or('#add-channel-button') );
		} else {
			$channel.appendTo('#channels');
		}

		if (chan.thumbnail) {
			$channel.find('.thumbnail')
				.css({
					'background-image': 'url(' + chan.thumbnail + ')'
				});
		}

		if ( !chan.thumbnail || chan.owner == 'user' ) { // get the newest thumbnail if it's a user channel
			self.apiCall(
				'channel_thumbnail',
				{ 'feed' : chan.feed },
				function(data) {
					var channel   = data[0],
						thumb	  = channel.thumbnail_url,
						chanIndex = self.getChan(channel.feed),
						chanObj   = self.Globals.channels[chanIndex];

					if (!thumb || thumb == '') return;

					if (chanObj) {
						chanObj.thumbnail = thumb;
						if (chanObj.owner == 'user') {
							self.Globals.user_channels[chanIndex] = chanObj;
							$.jStorage.set('user_channels', self.Globals.user_channels);
						}
					}
					$('#channels a.channel[data-feed="' + channel.feed + '"]').find('.thumbnail')
						.css({
							'background-image': 'url(' + thumb + ')'
						});

				}
			);
		}

		if (added) {
			self.bindChannelSorting(true);
			$channel.css({
				scale   : 0,
				opacity : 0
			})
			.delay(300)
			.transition({
				scale   : 1,
				opacity : 1
			}, 300);
		}

		return $channel;

		// $('#channel>ul').prepend('<li id="channel-'+chan+'" title="'+title+'" '+class_str+'><img src="http://i2.ytimg.com/vi/NUkwaiJgDGY/hqdefault.jpg" />'+display_title+remove_str+'</li>');

		/*$('#remove-'+chan).bind(
			'click',
			{channel: chan},
			function(event) {
				removeChan(event.data.channel);
			}
		);*/

	}, // displayChannel()

	loadChannel: function(channel, video_id) {
		// console.log('[loadChannel]', channel, video_id);
		var $video_embed = $('#video-embed'),
			$video_title = $('#video-title'),
			this_chan, title, getChan, anchor;

		getChan = self.getChanObj(channel.feed);
		this_chan = (getChan) ? getChan : channel;

		anchor = $('#channels a.channel[data-feed="' + this_chan.feed + '"]');
		if ( !anchor.length ) {
			this_chan.owner = 'temp';
			anchor = self.displayChannel(this_chan, true);
		}
		anchor.addClass('focus');

		self.Globals.shuffled = [];
		self.Globals.cur_chan = this_chan;
		
		/*$('#video-list').stop(true, true).animate({ height:0, padding:0 }, 500, function() {
			$(this).empty().hide();
		});*/
		$('#prev-button,#next-button').css({ 'visibility':'hidden', 'display':'none' });
		$('#vote-button').empty();
		$('#video-source').empty();

		title = channel.feed.split("/");
		title = "/"+title[1]+"/"+title[2];

		$video_title.html('Loading '+title+' ...');
		var thumbId = video_id;
		if (!thumbId) {
			var vidFeed = self.Globals.videos[channel.feed];
			if (vidFeed && vidFeed.video && vidFeed.video.length) thumbId = vidFeed.video[0].id;
		}
		var loadingThumb = (!thumbId && this_chan.thumbnail) ? this_chan.thumbnail : self.getThumbnailUrl(channel, thumbId);
		self.loadingAnimation(title, loadingThumb);
		$video_embed.empty();
		
		// TODO: Change to highlight the channel in the grid instead
		// $('#channel-list>ul>li').removeClass('chan-selected');
		// $('#channel-'+this_chan).addClass('chan-selected');

		var npTitle = self.Globals.cur_chan.feed;
		if (self.Globals.cur_chan.channel) npTitle = self.Globals.cur_chan.channel + ' - ' + npTitle;
		$('#now-playing-title').empty().append(npTitle+" &#9660;");

		
		if(self.Globals.videos[this_chan.feed] === undefined){
			self.redditApiCall('videos', { 'channel': this_chan, 'video_id': video_id },
				function(data, local) {
					var this_chan = local.channel,
					    video_id  = local.video_id;

					if (self.Globals.videos[this_chan.feed].video.length > 0) {
						if (video_id !== null) {
							self.loadVideoById(video_id);
						} else {
							self.loadVideoList(this_chan);
							self.Globals.cur_video = 0;
							self.loadVideo('first');
						}
					}
				},
				function(jXHR, textStatus, errorThrown, local) {
					self.tvError('Error loading channel.');
				});
		}else{
			if(self.Globals.videos[this_chan.feed].video.length > 0){
				if(video_id !== null){
					self.loadVideoById(video_id);
				}else{
					self.loadVideoList(this_chan);
					self.Globals.cur_video = 0;
					self.loadVideo('first');
				}
			}
		}
	}, // loadChannel()

	getFeedURI: function(channel_obj) {
		var sorting    = self.Globals.sorting.split(':'),
		    sortType   = '',
		    sortOption = '',
		    extras     = '',
		    limit      = 100,
		    uri;

		if (channel_obj.video_count && channel_obj.video_count < self.Globals.video_minimum) {
			limit = 1000;
			if (channel_obj.last_id) extras = '&after=' + channel_obj.last_id;
		}

		if (sorting.length === 2) {

			sortType = sorting[0] + '/';
			sortOption = '&t=' + sorting[1];
		}

		if (channel_obj.type === 'search' && sorting.length === 1) {

			uri = channel_obj.feed + Globals.search_str + '&limit=' + limit;
		}
		else {

			uri = channel_obj.feed + sortType + '.json?limit=' + limit + sortOption;
			// Can we do this with searching? sortType seems in the way.
			// uri = channel_obj.feed + sortType."/search/.json?q=%28and+%28or+site%3A%27youtube.com%27+site%3A%27vimeo.com%27+site%3A%27youtu.be%27%29+timestamp%3A1382227035..%29&restrict_sr=on&sort=top&syntax=cloudsearch&limit=100";

		}

		uri += extras;

		console.log(uri);
		return uri;
	}, // getFeedURI()

	isVideo: function(video_domain) {
		return (self.Globals.domains.indexOf(video_domain) !== -1);
	},

	isEmpty: function(obj) {
		for(var prop in obj) {
			if(obj.hasOwnProperty(prop)){
				return false;
			}
		}
		return true;
	}, // isEmpty()

	filterVideoDupes: function(arr) {
		var i, out=[], obj={}, original_length = arr.length;
		
		//work from last video to first video (so hottest dupe is left standing)
		//first pass on media embed
		for (i=arr.length-1; i>=0; i--) {
			if(typeof obj[arr[i].media_embed.content] !== 'undefined'){
				delete obj[arr[i].media_embed.content];
			}
			obj[arr[i].media_embed.content]=arr[i];
		}
		for (i in obj) {
			out.push(obj[i]);
		}

		arr = out.reverse();
		out = [];
		obj = {};

		//second pass on url
		for (i=arr.length-1; i>=0; i--) {
			if(typeof obj[arr[i].url] !== 'undefined'){
				delete obj[arr[i].url];
			}
			obj[arr[i].url]=arr[i];
		}
		for (i in obj) {
			out.push(obj[i]);
		}

		return out.reverse();
	}, // filterVideoDupes()

	findVideoById: function(id,chan) {
		for(var x in self.Globals.videos[chan].video){
			if(self.Globals.videos[chan].video[x].id === id){
				return Number(x); //if found return array pos
			}
		}
		return false; //not found
	}, // findVideoById()

	sfwCheck: function(video, chan) {
		return (self.Globals.sfw && self.Globals.videos[chan].video[video].over_18);
	},

	showHideNsfwThumbs: function(sfw) {
		$('.nsfw_thumb').toggleClass('visible', !sfw);
	},

	getThumbnailUrl: function(chan, video_id) {
		var video;
		if (typeof chan == 'object') {
			if (!chan.video) return false;
 			video = chan.video[video_id];
		} else {
			if (!self.Globals.videos[chan].video) return false;
			video = self.Globals.videos[chan].video[video_id];
		}

		if (video.media.oembed) {
			return video.media.oembed.thumbnail_url !== undefined ? 
				video.media.oembed.thumbnail_url :
				'img/noimage.png';
		}
		else {
			return 'img/noimage.png';
		}
	}, // getThumbnailUrl()

	createEmbed: function(url, type){
		switch(type){
		case 'youtube.com': case 'youtu.be':
			return youtube.createEmbed(url);
		case 'vimeo.com':
			return vimeo.createEmbed(url);
		default:
			return false;
		}
	}, // createEmbed()

	prepEmbed: function(embed, type) {
		// Flash and z-index on Windows fix
		if (!embed.match(/wmode/))
			embed = embed
				.replace(/<embed /, '<embed wmode="opaque" ')
				.replace(/<\/object>/, '<param name="wmode" value="opaque" /></object>');

		switch(type){
		case 'youtube.com': case 'youtu.be':
			return youtube.prepEmbed(embed);
		case 'vimeo.com':
			return vimeo.prepEmbed(embed);
		case 'size':
			embed = embed.replace(/height\="(\d\w+)"/gi, 'height="100%"');
			embed = embed.replace(/width\="(\d\w+)"/gi, 'width="100%"');
		}
		
		return embed;
	}, // prepEmbed()

	addListeners: function(type) {
		switch (type) {
			case 'vimeo.com':
				vimeo.addListeners();
		}
	}, // addListeners()

	apiCall: function(action, data, successCallback, errorCallback) {
		var apiData = $.extend({ 'action' : action }, (typeof data != 'object') ? {} : data );

		if ( !$.isFunction(successCallback) ) successCallback = function(){};
		if ( !$.isFunction(errorCallback) )	errorCallback	= function(){};

		$.ajax({
			url: self.Globals.current_url + 'db/api.php',
			data: apiData,
			dataType: 'json',
			success: successCallback,
			error: function(jXHR, textStatus, errorThrown) {
				console.log('[apiCall]', action);
				console.log('[ERROR]', textStatus);
				console.log('[ERROR]', errorThrown);
				errorCallback(jXHR, textStatus, errorThrown);
			}
		});
	}, // apiCall()

	redditApiCall: function(action, data, successCallback, errorCallback) {
		if ( !$.isFunction(successCallback) ) successCallback = function(){};
		if ( !$.isFunction(errorCallback) )	errorCallback	= function(){};

		var last_req = self.Globals.cur_chan_req;
		if (last_req !== null) last_req.abort();

		var redditApiError = function(jXHR, textStatus, errorThrown, local) {
			if(textStatus !== 'abort') {
				// alert('Could not load feed. Is reddit down?');
			}

			console.log(jXHR, textStatus, errorThrown);
			errorCallback(jXHR, textStatus, errorThrown, local);

			$('body').removeClass('video-loading');
		}

		if (action == 'videos') {
			var video_id  = data.video_id,
			    this_chan = data.channel;

			self.Globals.cur_chan_req = $.ajax({
				url: 'http://www.reddit.com' + self.getFeedURI(this_chan),
				dataType: 'jsonp',
				jsonp: 'jsonp',
				timeout: 5000, // Server doesn't return JSON on error, have to rely on this.
				context: { 'action' : action, 'data' : data, 'successCallback' : successCallback, 'errorCallback' : errorCallback },
				success: function(data, textStatus) {
					var this_chan  = this.data.channel,
					    chan_index = self.getChan(this_chan.feed);

					//clear out stored videos if not grabbing more videos
					if ( !this_chan.last_id || !self.Globals.videos[this_chan.feed] ) {
						self.Globals.videos[this_chan.feed] = {};
						self.Globals.videos[this_chan.feed].video = [];
					}

					for (var x in data.data.children) {
						if ( !this_chan.channel ) this_chan.channel = data.data.children[x].data.subreddit;

						if (self.isVideo(data.data.children[x].data.domain) && (data.data.children[x].data.score > 1)) {
							if ( self.isEmpty(data.data.children[x].data.media_embed) || data.data.children[x].data.domain === 'youtube.com' || data.data.children[x].data.domain === 'youtu.be' ){
								var created = self.createEmbed(data.data.children[x].data.url, data.data.children[x].data.domain);
								if (created !== false) {
									data.data.children[x].data.media_embed.content = created.embed;
									data.data.children[x].data.media = {};
									data.data.children[x].data.media.oembed = {};
									data.data.children[x].data.media.oembed.thumbnail_url = created.thumbnail;
								}
							}
							if (data.data.children[x].data.media_embed.content) {
								self.Globals.videos[this_chan.feed].video.push(data.data.children[x].data);
							}
						}

						this_chan.last_id = data.data.children[x].data.name;
					}

					//remove duplicates
					self.Globals.videos[this_chan.feed].video = self.filterVideoDupes(self.Globals.videos[this_chan.feed].video);
					this.data.videos = self.Globals.videos[this_chan.feed].video;
					this.data.data = data;

					if (!this.data.videos.length) return redditApiError(null, textStatus, null, this.data);

					if (data.data.children.length && this.data.videos.length < self.Globals.video_minimum) {
						this_chan.page = (this_chan.page) ? this_chan.page + 1 : 2;
						this_chan.video_count = this.data.videos.length;

						if (this_chan.page > 5 && this_chan.video_count == 0) {
							self.tvError('No videos found in ' + this_chan.channel);
						} else {
							self.redditApiCall(this.action, this.data, this.successCallback, this.errorCallback);
						}

						var moreVideos = true;
					}

					self.Globals.channels[self.getChan(this_chan.feed)] = this_chan;

					if (moreVideos) return;

					successCallback(data, this.data);

					$('body').removeClass('video-loading');
{'fraction_non_alphanumeric': 0.19532526969597908, 'fraction_numerical': 0.008156260215756782, 'mean_word_length': 3.34987557767508, 'pattern_counts': {'":': 0, '<': 59, '<?xml version=': 0, '>': 75, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '543944', 'n_tokens_mistral': 26188, 'n_tokens_neox': 21324, 'n_words': 3653}