PageSourceSearch

https://www.duplexrecords.no/wp-content/themes/duplex/js/ytv.js

js duplexrecords.no collected 2026-10-02 13:04:11 UTC 21,315 bytes, 562 lines download raw bytes

1/*
2 * YouTube TV
3 *
4 * Copyright 2013, Jacob Kelley - http://jakiestfu.com/
5 * Released under the MIT Licence
6 * http://opensource.org/licenses/MIT
7 *
8 * Github:
9 * Version: 3.0.5
10 */
11/*jslint browser: true, undef:true, unused:true, laxbreak:true, loopfunc:true*/
12/*global define, module, ender */
13
14(function(win, doc) {
15	'use strict';
16	var apiKey = 'AIzaSyAv791fSByhxiHw9DEl1FCSgghnEPFABIA';
17	var YTV = YTV || function(id, opts){
18
19		var noop = function(){},
20			settings = {
21				apiKey: apiKey,
22				element: null,
23				user: null,
24				channelId: null,
25				fullscreen: false,
26				accent: '#fff',
27				controls: true,
28				annotations: false,
29				autoplay: false,
30				chainVideos: true,
31				browsePlaylists: false,
32				playerTheme: 'dark',
33				listTheme: 'dark',
34				responsive: false,
35				playId:'',
36				sortList: false,
37				reverseList: false,
38				shuffleList: false,
39				wmode: 'opaque',
40				events: {
41					videoReady: noop,
42					stateChange: noop
43				}
44			},
45
46			cache = {
47				data: {},
48				remove: function (url) {
49					delete cache.data[url];
50				},
51				exist: function (url) {
52					return cache.data.hasOwnProperty(url) && cache.data[url] !== null;
53				},
54				get: function (url) {
55					return cache.data[url];
56				},
57				set: function (url, data) {
58					cache.remove(url);
59					cache.data[url] = data;
60				}
61			},
62			utils = {
63				events: {
64					addEvent: function addEvent(element, eventName, func) {
65						if (element.addEventListener) {
66							return element.addEventListener(eventName, func, false);
67						} else if (element.attachEvent) {
68							return element.attachEvent("on" + eventName, func);
69						}
70					},
71					removeEvent: function addEvent(element, eventName, func) {
72						if (element.addEventListener) {
73							return element.removeEventListener(eventName, func, false);
74						} else if (element.attachEvent) {
75							return element.detachEvent("on" + eventName, func);
76						}
77					},
78					prevent: function(e) {
79						if (e.preventDefault) {
80							e.preventDefault();
81						} else {
82							e.returnValue = false;
83						}
84					}
85				},
86				addCSS: function(css){
87					var head = doc.getElementsByTagName('head')[0],
88						style = doc.createElement('style');
89						style.type = 'text/css';
90					if (style.styleSheet){
91						style.styleSheet.cssText = css;
92					} else {
93						style.appendChild(doc.createTextNode(css));
94					}
95					head.appendChild(style);
96				},
97				addCommas: function(str){
98					var x = str.split('.'),
99						x1 = x[0],
100						x2 = x.length > 1 ? '.' + x[1] : '',
101						rgx = /(\d+)(\d{3})/;
102					while (rgx.test(x1)) {
103						x1 = x1.replace(rgx, '$1' + ',' + '$2');
104					}
105					return x1 + x2;
106				},
107				parentUntil: function(el, attr) {
108					while (el.parentNode) {
109						if (el.getAttribute && el.getAttribute(attr)){
110							return el;
111						}
112						el = el.parentNode;
113					}
114					return null;
115				},
116				ajax: {
117					get: function(url, fn){
118						if (cache.exist(url)) {
119							fn.call(this, JSON.parse(cache.get(url)));
120						} else {
121							var handle;
122							if (win.XDomainRequest && !(navigator.appVersion.indexOf("MSIE 8")==-1 || navigator.appVersion.indexOf("MSIE 9")==-1)) { // CORS for IE8,9
123								handle = new XDomainRequest();
124								handle.onload = function(){
125									cache.set(url, handle.responseText);
126									fn.call(this, JSON.parse(handle.responseText));
127									if (Object.prototype.hasOwnProperty.call(JSON.parse(handle.responseText), 'error')){
128										cache.remove(url);
129										var e = JSON.parse(handle.responseText);
130										console.log('Youtube-TV Error: Youtube API Response: '+e.error.errors[0].reason+'\n'+ 'Details: '+e.error.errors[0].message);
131									}
132								};
133							} else if (win.XMLHttpRequest){ // Modern Browsers
134								handle = new XMLHttpRequest();
135							}
136							handle.onreadystatechange = function(){
137								if (handle.readyState === 4 && handle.status === 200){
138									cache.set(url, handle.responseText);
139									fn.call(this, JSON.parse(handle.responseText));
140								} else if (handle.readyState === 4){
141									var e = JSON.parse(handle.responseText);
142									console.log('Youtube-TV Error: Youtube API Response: '+e.error.errors[0].reason+'\n'+ 'Details: '+e.error.errors[0].message);
143								}
144							};
145							handle.open("GET",url,true);
146							handle.send();
147						}
148					}
149				},
150				endpoints: {
151					base: 'https://www.googleapis.com/youtube/v3/',
152					userInfo: function(){
153						return utils.endpoints.base+'channels?'+settings.cid+'&key='+apiKey+'&part=snippet,contentDetails,statistics';
154					},
155					playlistInfo: function(pid){
156						return utils.endpoints.base+'playlists?id='+pid+'&key='+apiKey+'&maxResults=50&part=snippet';
157					},
158					userPlaylists: function(){
159						return utils.endpoints.base+'playlists?channelId='+settings.channelId+'&key='+apiKey+'&maxResults=50&part=snippet';
160					},
161					playlistVids: function(){
162						return utils.endpoints.base+'playlistItems?playlistId='+settings.pid+'&key='+apiKey+'&maxResults=50&part=contentDetails';
163					},
164					videoInfo: function(){
165						return utils.endpoints.base+'videos?id='+settings.videoString+'&key='+apiKey+'&maxResults=50&part=snippet,contentDetails,status,statistics';
166					}
167				},
168				deepExtend: function(destination, source) {
169					var property;
170					for (property in source) {
171						if (source[property] && source[property].constructor && source[property].constructor === Object) {
172							destination[property] = destination[property] || {};
173							utils.deepExtend(destination[property], source[property]);
174						} else {
175							destination[property] = source[property];
176						}
177					}
178					return destination;
179				}
180			},
181			prepare = {
182				youtube: function(){
183					if(typeof YT=='undefined'){
184						var tag = doc.createElement('script');
185						tag.src = "https://www.youtube.com/iframe_api";
186						var firstScriptTag = doc.getElementsByTagName('script')[0];
187						firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
188					}
189				},
190				build: function(){
191					if (settings.channelId){
192						settings.cid = 'id='+settings.channelId;
193					} else if(settings.user){
194						settings.cid = 'forUsername='+settings.user;
195					}
196					settings.element.className = "ytv-canvas";
197					if(settings.fullscreen){
198						settings.element.className += " ytv-full";
199					}
200					utils.addCSS( '#'+id+' .ytv-list .ytv-active a{border-left-color: '+(settings.accent)+';}' );
201					// Responsive CSS
202					if(settings.responsive){
203						utils.addCSS('#'+id+' .ytv-video{'
204							+'position: relative; padding-bottom: 39.4%; /* 16:9 of 70%*/'
205							+'height: 0; width: 70%;'
206							+'} #'+id+' .ytv-video iframe{'
207							+'position: absolute; top: 0; left: 0;'
208							+'} #'+id+' .ytv-list{'
209							+'width: 30%;'
210							+'} #'+id+' .ytv-playlist-open .ytv-arrow{'
211							+'top: 0px;}'
212							+'@media only screen and (max-width:992px) {'
213							+'#'+id+' .ytv-list{'
214							+'position: relative; display: block;'
215							+'width: 0; padding-bottom: 40%;'
216							+'left: auto; right: auto;'
217							+'top: auto; width: 100%;'
218							+'} #'+id+' .ytv-video{'
219							+'position: relative; padding-bottom: 56.25%; /* 16:9 */'
220							+'height: 0; position: relative;'
221							+'display: block; left: auto;'
222							+'right: auto; top: auto; width: 100%;'
223							+'}}'
224							);
225					}
226					// Temp Scroll Bar fix
227					if (settings.listTheme == 'dark'){
228						utils.addCSS( ' #'+id+'.ytv-canvas ::-webkit-scrollbar{border-left: 1px solid #000;}'
229							+ ' #'+id+'.ytv-canvas ::-webkit-scrollbar-thumb{background: rgba(255,255,255,0.2);}');
230					}
231					// Optional Light List Theme
232					if(settings.listTheme == 'light'){
233						utils.addCSS( ' #'+id+'.ytv-canvas{background: #ccc;}'
234							+ ' #'+id+'.ytv-canvas ::-webkit-scrollbar{border-left: 1px solid rgba(28,28,28,0.1);}'
235							+ ' #'+id+'.ytv-canvas ::-webkit-scrollbar-thumb{background: rgba(28,28,28,0.3);}'
236							+ ' #'+id+' .ytv-list .ytv-active a{background: rgba(0,0,0,0.2);}'
237							+ ' #'+id+' .ytv-list a{color: #282828; border-top: 1px solid rgba(0,0,0,0.1); border-bottom: 1px solid rgba(204,204,204,0.5);}'
238							+ ' #'+id+' .ytv-list a:hover, #'+id+' .ytv-list-header .ytv-playlists a:hover{ background: rgba(0,0,0,0.2);}'
239							+ ' #'+id+' .ytv-list a:active, #'+id+' .ytv-list-header .ytv-playlists a:active{ background: rgba(0,0,0,0.2);}'
240							+ ' #'+id+' .ytv-list .ytv-thumb-stroke{outline: 1px solid rgba(0,0,0,0.1);}'
241							+ ' #'+id+' .ytv-list .ytv-thumb{outline: 1px solid rgba(255,255,255,0.5);}'
242							+ ' #'+id+' .ytv-list-header{-webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.2); -moz-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.2); box-shadow: 0 1px 2px rgba(255, 255, 255, 0.2);}'
243							+ ' #'+id+' .ytv-list-header a{background: rgba(0,0,0,0.2);}'
244							+ ' #'+id+' .ytv-playlists{background: #ccc;}'
245							);
246					}
247				},
248				userUploads: function(userInfo){
249					if (userInfo && userInfo.items.length > 0){
250						settings.pid = userInfo.items[0].contentDetails.relatedPlaylists.uploads;
251						utils.ajax.get( utils.endpoints.playlistVids(), prepare.compileVideos );
252					} else console.log ('Youtube-TV Error: API returned no matches for: '+(settings.channelId ? settings.channelId : settings.user)+'\nPlease ensure it was entered correctly and in the appropriate field shown below. \nuser: \'username\' or channelId: \'UCxxxx...\'');
253				},
254				selectedPlaylist: function(playlistInfo){
255					if (playlistInfo && playlistInfo.items.length > 0) {
256						if (!settings.channelId && !settings.user){
257							settings.cid = ('id='+(settings.channelId = playlistInfo.items[0].snippet.channelId));
258						}
259						settings.currentPlaylist = playlistInfo.items[0].snippet.title;
260						settings.pid = playlistInfo.items[0].id;
261						utils.ajax.get( utils.endpoints.playlistVids(), prepare.compileVideos );
262					} else console.log ('Youtube-TV Error: API returned no matches for playlist(s): '+settings.playlist);
263				},
264				compileVideos: function(res){
265					if (res && res.items.length > 0){
266						var playlists = res.items,
267						i;
268						settings.videoString = '';
269						for(i=0; i<playlists.length; i++){
270							settings.videoString += playlists[i].contentDetails.videoId;
271							if (i<playlists.length-1){ settings.videoString += ',';}
272						}
273						utils.ajax.get( utils.endpoints.videoInfo(), prepare.compileList );
274					} else console.log ('Youtube-TV Error: Empty playlist');
275				},
276				playlists: function(res){
277					if(res && res.items.length > 0){
278						var list = '<div class="ytv-playlists"><ul>',
279							playlists = res.items,
280							i;
281						for(i=0; i<playlists.length; i++){
282							var data = {
283								title: playlists[i].snippet.title,
284								plid: playlists[i].id,
285								thumb: playlists[i].snippet.thumbnails.medium.url
286							};
287							list += '<a href="#" data-ytv-playlist="'+(data.plid)+'">';
288								list += '<div class="ytv-thumb"><div class="ytv-thumb-stroke"></div><img src="'+(data.thumb)+'"></div>';
289								list += '<span>'+(data.title)+'</span>';
290							list += '</a>';
291						}
292						list += '</ul></div>';
293
294						var lh = settings.element.getElementsByClassName('ytv-list-header')[0],
295							headerLink = lh.children[0];
296						headerLink.href="#";
297						headerLink.target="";
298						headerLink.setAttribute('data-ytv-playlist-toggle', 'true');
299						settings.element.getElementsByClassName('ytv-list-header')[0].innerHTML += list;
300						lh.className += ' ytv-has-playlists';
301					} else console.log ('Youtube-TV Error: Returned no playlists');
302				},
303				compileList: function(data){
304					if(data && data.items.length > 0){
305						utils.ajax.get( utils.endpoints.userInfo(), function(userInfo){
306							var list = '',
307								user = {
308									title: userInfo.items[0].snippet.title,
309									url: '//youtube.com/channel/'+userInfo.items[0].id,
310									thumb: userInfo.items[0].snippet.thumbnails['default'].url,
311									summary: userInfo.items[0].snippet.description,
312									subscribers: userInfo.items[0].statistics.subscriberCount,
313									views: userInfo.items[0].statistics.viewCount
314								},
315								videos = data.items,
316								first = true,
317								i;
318							settings.channelId = userInfo.items[0].id;
319							if(settings.currentPlaylist) user.title += ' &middot; '+(settings.currentPlaylist);
320							if (settings.sortList) videos.sort(function(a,b){if(a.snippet.publishedAt > b.snippet.publishedAt) return -1;if(a.snippet.publishedAt < b.snippet.publishedAt) return 1;return 0;});
321							if (settings.reverseList) videos.reverse();
322							if (settings.shuffleList) {
323								videos = function (){for(var j, x, i = videos.length; i; j = Math.floor(Math.random() * i), x = videos[--i], videos[i] = videos[j], videos[j] = x);return videos;}();
324							}
325
326							list += '<div class="ytv-list-header">';
327								list += '<a href="'+(user.url)+'" target="_blank">';
328									list += '<img src="'+(user.thumb)+'">';
329									list += '<span><i class="ytv-arrow down"></i>'+(user.title)+'</span>';
330								list += '</a>';
331							list += '</div>';
332
333							list += '<div class="ytv-list-inner"><ul>';
334							for(i=0; i<videos.length; i++){
335								if(videos[i].status.embeddable){
336									var video = {
337										title: videos[i].snippet.title,
338										slug: videos[i].id,
339										link: 'https://www.youtube.com/watch?v='+videos[i].id,
340										published: videos[i].snippet.publishedAt,
341										stats: videos[i].statistics,
342										duration: (videos[i].contentDetails.duration),
343										thumb: videos[i].snippet.thumbnails.medium.url
344									};
345
346									var durationString = video.duration.match(/[0-9]+[HMS]/g);
347									var h = 0, m = 0, s = 0, time = '';
348									durationString.forEach(function (duration) {
349										var unit = duration.charAt(duration.length-1);
350										var amount = parseInt(duration.slice(0,-1));
351										switch (unit) {
352											case 'H': h = (amount > 9 ? '' + amount : '0' + amount); break;
353											case 'M': m = (amount > 9 ? '' + amount : '0' + amount); break;
354											case 'S': s = (amount > 9 ? '' + amount : '0' + amount); break;
355										}
356									});
357									if (h){ time += h+':';}
358									if (m){ time += m+':';} else { time += '00:';}
359									if (s){ time += s;} else { time += '00';}
360
361									var isFirst = '';
362									if(settings.playId==video.slug){
363										isFirst = ' class="ytv-active"';
364										first = video.slug;
365									} else if(first===true){
366										first = video.slug;
367									}
368
369									list += '<li'+isFirst+'><a href="#" data-ytv="'+(video.slug)+'" class="ytv-clear">';
370									list += '<div class="ytv-thumb"><div class="ytv-thumb-stroke"></div><span>'+(time)+'</span><img src="'+(video.thumb)+'"></div>';
371									list += '<div class="ytv-content"><b>'+(video.title)+'</b>';
372									if (video.stats)
373									{
374										list+='</b><span class="ytv-views">'+utils.addCommas(video.stats.viewCount)+' Views</span>';
375									}
376									list += '</div></a></li>';
377								}
378							}
379							list += '</ul></div>';
380							settings.element.innerHTML = '<div class="ytv-relative"><div class="ytv-video"><div id="ytv-video-player"></div></div><div class="ytv-list">'+list+'</div></div>';
381							if(settings.element.getElementsByClassName('ytv-active').length===0){
382								settings.element.getElementsByTagName('li')[0].className = "ytv-active";
383							}
384							var active = settings.element.getElementsByClassName('ytv-active')[0];
385							active.parentNode.parentNode.scrollTop = active.offsetTop;
386							action.logic.loadVideo(first, settings.autoplay);
387
388							if (settings.playlist){
389								utils.ajax.get( utils.endpoints.playlistInfo(settings.playlist), prepare.playlists );
390							} else if(settings.browsePlaylists){
391								utils.ajax.get( utils.endpoints.userPlaylists(), prepare.playlists );
392							}
393
394						});
395					} else console.log ('Youtube-TV Error: Empty video list');
396				}
397			},
398			action = {
399
400				logic: {
401
402					playerStateChange: function(d){
403						console.log(d);
404					},
405
406					loadVideo: function(slug, autoplay){
407						var house = settings.element.getElementsByClassName('ytv-video')[0];
408						var counter = settings.element.getElementsByClassName('ytv-video-playerContainer').length;
409						house.innerHTML = '<div id="ytv-video-player'+id+counter+'" class="ytv-video-playerContainer"></div>';
410
411						cache.player = new YT.Player('ytv-video-player'+id+counter, {
412							videoId: slug,
413							events: {
414								onReady: settings.events.videoReady,
415								onStateChange: function(e){
416									if( (e.target.getPlayerState()===0) && settings.chainVideos ){
417										var ns = settings.element.getElementsByClassName('ytv-active')[0].nextSibling,
418											link = ns.children[0];
419										link.click();
420									}
421									settings.events.stateChange.call(this, e);
422								}
423							},
424							playerVars: {
425								enablejsapi: 1,
426								origin: doc.domain,
427								controls: settings.controls ? 1 : 0,
428								rel: 0,
429								showinfo: 0,
430								iv_load_policy: settings.annotations ? '' : 3,
431								autoplay: autoplay ? 1 : 0,
432								theme: settings.playerTheme,
433								wmode: settings.wmode
434							}
435						});
436					}
437				},
438
439				endpoints: {
440					videoClick: function(e){
441						var target = utils.parentUntil(e.target ? e.target : e.srcElement, 'data-ytv');
442						if(target){
443							if(target.getAttribute('data-ytv')){
444								// Load Video
445								utils.events.prevent(e);
446								var activeEls = settings.element.getElementsByClassName('ytv-active'),
447									i;
448								for(i=0; i<activeEls.length; i++){
449									activeEls[i].className="";
450								}
451								target.parentNode.className="ytv-active";
452								action.logic.loadVideo(target.getAttribute('data-ytv'), true);
453							}
454						}
455					},
456					playlistToggle: function(e){
457						var target = utils.parentUntil(e.target ? e.target : e.srcElement, 'data-ytv-playlist-toggle');
458						if(target && target.getAttribute('data-ytv-playlist-toggle')){
459							// Toggle Playlist
460							utils.events.prevent(e);
461							var lh = settings.element.getElementsByClassName('ytv-list-header')[0];
462							if(lh.className.indexOf('ytv-playlist-open')===-1){
463								lh.className += ' ytv-playlist-open';
464							} else {
465								lh.className = lh.className.replace(' ytv-playlist-open', '');
466							}
467						}
468					},
469					playlistClick: function(e){
470						var target = utils.parentUntil(e.target ? e.target : e.srcElement, 'data-ytv-playlist');
471
472						if(target && target.getAttribute('data-ytv-playlist')){
473
474							// Load Playlist
475							utils.events.prevent(e);
476
477							settings.pid = target.getAttribute('data-ytv-playlist');
478							target.children[1].innerHTML = 'Loading...';
479
480							utils.ajax.get( utils.endpoints.playlistInfo(settings.pid), function(res){
481								var lh = settings.element.getElementsByClassName('ytv-list-header')[0];
482								lh.className = lh.className.replace(' ytv-playlist-open', '');
483								prepare.selectedPlaylist(res);
484							});
485						}
486					}
487				},
488				bindEvents: function(){
489					utils.events.addEvent( settings.element, 'click', action.endpoints.videoClick );
490					utils.events.addEvent( settings.element, 'click', action.endpoints.playlistToggle );
491					utils.events.addEvent( settings.element, 'click', action.endpoints.playlistClick );
492				}
493			},
494
495			initialize = function(id, opts){
496				utils.deepExtend(settings, opts);
497				if(settings.apiKey.length===0){
498					alert("Missing APIkey in settings or as global vaiable.");
499				}
500				apiKey = settings.apiKey;
501				settings.element = (typeof id==='string') ? doc.getElementById(id) : id;
502				if(settings.element && (settings.user || settings.channelId || settings.playlist)){
503					prepare.youtube();
504					prepare.build();
505					action.bindEvents();
506					if (settings.playlist) {
507						utils.ajax.get( utils.endpoints.playlistInfo(settings.playlist), prepare.selectedPlaylist );
508					} else {
509						utils.ajax.get( utils.endpoints.userInfo(), prepare.userUploads );
510					}
511				} else console.log ('Youtube-TV Error: Missing either user, channelId, or playlist');
512			};
513
514			/* Public */
515
516			this.destroy = function(){
517				utils.events.removeEvent( settings.element, 'click', action.endpoints.videoClick );
518				utils.events.removeEvent( settings.element, 'click', action.endpoints.playlistToggle );
519				utils.events.removeEvent( settings.element, 'click', action.endpoints.playlistClick );
520				settings.element.className = '';
521				settings.element.innerHTML = '';
522			};
523			this.fullscreen = {
524				state: function(){
525					return (settings.element.className).indexOf('ytv-full') !== -1;
526				},
527				enter: function(){
528					if( (settings.element.className).indexOf('ytv-full') === -1 ){
529						settings.element.className += 'ytv-full';
530					}
531				},
532				exit: function(){
533					if( (settings.element.className).indexOf('ytv-full') !== -1 ){
534						settings.element.className = (settings.element.className).replace('ytv-full', '');
535					}
536				}
537			};
538
539		initialize(id, opts);
540	};
541
542	if ((typeof module !== 'undefined') && module.exports) {
543		module.exports = YTV;
544	}
545	if (typeof ender === 'undefined') {
546		this.YTV = YTV;
547	}
548	if ((typeof define === "function") && define.amd) {
549		define("YTV", [], function() {
550			return YTV;
551		});
552	}
553	if ((typeof jQuery !== 'undefined')) {
554		jQuery.fn.extend({
555			ytv: function(options) {
556				return this.each(function() {
557					new YTV(this.id, options);
558				});
559			}
560		});
561	}
562}).call(this, window, document);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.