PageSourceSearch

https://art21.org/wp-content/themes/art21_dev_a11y/js/vg-modules/vg-controls.js?v=1.06&ver=7.0.5

js art21.org collected 2026-10-01 09:16:39 UTC 30,912 bytes, 1,067 lines download raw bytes

1"use strict";
2angular.module("com.2fdevs.videogular.plugins.controls", [])
3	.run(
4	["$templateCache", function ($templateCache) {
5		$templateCache.put("vg-templates/vg-controls",
6			'<div class="controls-container" ng-mousemove="onMouseMove()" ng-class="animationClass" ng-transclude></div>');
7	}]
8)
9	.directive("vgControls",
10	["$timeout", "VG_STATES", function ($timeout, VG_STATES) {
11		return {
12			restrict: "E",
13			require: "^videogular",
14			transclude: true,
15			templateUrl: function (elem, attrs) {
16				return attrs.vgTemplate || 'vg-templates/vg-controls';
17			},
18			scope: {
19				vgAutohide: "=?",
20				vgAutohideTime: "=?"
21			},
22			link: function (scope, elem, attr, API) {
23				var w = 0;
24				var h = 0;
25				var autoHideTime = 2000;
26				var hideInterval;
27
28				scope.API = API;
29
30				scope.onMouseMove = function onMouseMove() {
31					if (scope.vgAutohide) scope.showControls();
32				};
33
34				scope.setAutohide = function setAutohide(value) {
35					if (value && API.currentState == VG_STATES.PLAY) {
36						hideInterval = $timeout(scope.hideControls, autoHideTime);
37					}
38					else {
39						scope.animationClass = "";
40						$timeout.cancel(hideInterval);
41						scope.showControls();
42					}
43				};
44
45				scope.setAutohideTime = function setAutohideTime(value) {
46					autoHideTime = value;
47				};
48
49				scope.hideControls = function hideControls() {
50					scope.animationClass = "hide-animation";
51				};
52
53				scope.showControls = function showControls() {
54					scope.animationClass = "show-animation";
55					$timeout.cancel(hideInterval);
56					if (scope.vgAutohide && API.currentState == VG_STATES.PLAY) hideInterval = $timeout(scope.hideControls, autoHideTime);
57				};
58
59				if (API.isConfig) {
60					scope.$watch("API.config",
61						function () {
62							if (scope.API.config) {
63								var ahValue = scope.API.config.plugins.controls.autohide || false;
64								var ahtValue = scope.API.config.plugins.controls.autohideTime || 2000;
65								scope.vgAutohide = ahValue;
66								scope.vgAutohideTime = ahtValue;
67								scope.setAutohideTime(ahtValue);
68								scope.setAutohide(ahValue);
69							}
70						}
71					);
72				}
73				else {
74					// If vg-autohide has been set
75					if (scope.vgAutohide != undefined) {
76						scope.$watch("vgAutohide", scope.setAutohide);
77					}
78
79					// If vg-autohide-time has been set
80					if (scope.vgAutohideTime != undefined) {
81						scope.$watch("vgAutohideTime", scope.setAutohideTime);
82					}
83				}
84
85				scope.$watch(
86					function () {
87						return API.currentState;
88					},
89					function (newVal, oldVal) {
90						if (scope.vgAutohide) scope.showControls();
91					}
92				);
93			}
94		}
95	}]
96);
97
98angular.module("com.2fdevs.videogular.plugins.controls")
99	.run(
100	["$templateCache", function ($templateCache) {
101		$templateCache.put("vg-templates/vg-fullscreen-button",
102			'<button class="iconButton fullscreenIcon" ng-click="onClickFullScreen()" aria-label="Toggle full screen" type="button"><div class="tooltip">Fullscreen</div></button>');
103	}]
104)
105	.directive("vgFullscreenButton",
106	[function () {
107		return {
108			restrict: "E",
109			require: "^videogular",
110			scope: {},
111			templateUrl: function (elem, attrs) {
112				return attrs.vgTemplate || 'vg-templates/vg-fullscreen-button';
113			},
114			link: function (scope, elem, attr, API) {
115				scope.onChangeFullScreen = function onChangeFullScreen(isFullScreen) {
116					scope.fullscreenIcon = {enter: !isFullScreen, exit: isFullScreen};
117				};
118
119				scope.onClickFullScreen = function onClickFullScreen() {
120					API.toggleFullScreen();
121				};
122
123				scope.fullscreenIcon = {enter: true};
124
125				scope.$watch(
126					function () {
127						return API.isFullScreen;
128					},
129					function (newVal, oldVal) {
130						if (newVal != oldVal) {
131							scope.onChangeFullScreen(newVal);
132						}
133					}
134				);
135			}
136		}
137	}]
138);
139
140angular.module("com.2fdevs.videogular.plugins.controls")
141	.run(
142	["$templateCache", function ($templateCache) {
143		$templateCache.put("vg-templates/vg-play-pause-button",
144			'<button class="iconButton" ng-click="onClickPlayPause()" ng-class="playPauseIcon" aria-label="Play/Pause" type="button"></button>');
145	}]
146)
147	.directive("vgPlayPauseButton",
148	["VG_STATES", function (VG_STATES) {
149		return {
150			restrict: "E",
151			require: "^videogular",
152			scope: {},
153			templateUrl: function (elem, attrs) {
154				return attrs.vgTemplate || 'vg-templates/vg-play-pause-button';
155			},
156			link: function (scope, elem, attr, API) {
157				scope.setState = function setState(newState) {
158					switch (newState) {
159						case VG_STATES.PLAY:
160							scope.playPauseIcon = {pause: true};
161							break;
162
163						case VG_STATES.PAUSE:
164							scope.playPauseIcon = {play: true};
165							break;
166
167						case VG_STATES.STOP:
168							scope.playPauseIcon = {play: true};
169							break;
170					}
171				};
172
173				scope.onClickPlayPause = function onClickPlayPause() {
174					API.playPause();
175				};
176
177				scope.playPauseIcon = {play: true};
178
179				scope.$watch(
180					function () {
181						return API.currentState;
182					},
183					function (newVal, oldVal) {
184						scope.setState(newVal);
185					}
186				);
187			}
188		};
189	}]
190);
191
192angular.module("com.2fdevs.videogular.plugins.controls")
193	.run(
194	["$templateCache", function ($templateCache) {
195		$templateCache.put("vg-templates/vg-playback-button",
196			'<button class="playbackValue iconButton" ng-click="onClickPlayback()">{{playback}}x</button>');
197	}]
198)
199	.directive("vgPlaybackButton",
200	[function () {
201		return {
202			restrict: "E",
203			require: "^videogular",
204			templateUrl: function (elem, attrs) {
205				return attrs.vgTemplate || 'vg-templates/vg-playback-button';
206			},
207			scope: {
208				vgSpeeds: '=?'
209			},
210			link: function (scope, elem, attr, API) {
211				scope.playback = '1';
212
213				scope.setPlayback = function(playback) {
214					scope.playback = playback;
215					API.setPlayback(parseFloat(playback));
216				};
217
218				scope.onClickPlayback = function onClickPlayback() {
219					var playbackOptions = scope.vgSpeeds || ['0.5', '1', '1.5', '2'];
220					var nextPlaybackRate = playbackOptions.indexOf(scope.playback.toString()) + 1;
221
222					if (nextPlaybackRate >= playbackOptions.length) {
223						scope.playback = playbackOptions[0];
224					}
225					else {
226						scope.playback = playbackOptions[nextPlaybackRate];
227					}
228
229					scope.setPlayback(scope.playback);
230				};
231
232				scope.$watch(
233					function () {
234						return API.playback;
235					},
236					function(newVal, oldVal) {
237						if (newVal != oldVal) {
238							scope.setPlayback(newVal);
239						}
240					}
241				);
242			}
243		}
244	}]
245);
246
247angular.module("com.2fdevs.videogular.plugins.controls")
248	.directive("vgScrubBarBuffer",
249	[function () {
250		return {
251			restrict: "E",
252			require: "^videogular",
253			link: function (scope, elem, attr, API) {
254				var percentTime = 0;
255
256				scope.onUpdateBuffer = function onUpdateBuffer(newBuffer) {
257					if (typeof newBuffer === 'number' && API.totalTime) {
258						percentTime = 100 * (newBuffer / API.totalTime);
259						elem.css("width", percentTime + "%");
260					} else {
261						elem.css("width", 0);
262					}
263				};
264
265				scope.$watch(
266					function () {
267						return API.bufferEnd;
268					},
269					function (newVal, oldVal) {
270						scope.onUpdateBuffer(newVal);
271					}
272				);
273			}
274		}
275	}]
276);
277
278angular.module("com.2fdevs.videogular.plugins.controls")
279	.directive("vgScrubBarCurrentTime",
280	[function () {
281		return {
282			restrict: "E",
283			require: "^videogular",
284			link: function (scope, elem, attr, API) {
285				var percentTime = 0;
286
287				scope.onUpdateTime = function onUpdateTime(newCurrentTime) {
288					if (typeof newCurrentTime === 'number' && API.totalTime) {
289						percentTime = 100 * (newCurrentTime / API.totalTime);
290						elem.css("width", percentTime + "%");
291					} else {
292						elem.css("width", 0);
293					}
294				};
295
296				scope.$watch(
297					function () {
298						return API.currentTime;
299					},
300					function (newVal, oldVal) {
301						scope.onUpdateTime(newVal);
302					}
303				);
304			}
305		}
306	}]
307);
308
309angular.module("com.2fdevs.videogular.plugins.controls")
310	.run(["$templateCache",
311		function ($templateCache) {
312			$templateCache.put("vg-templates/vg-scrub-bar",
313				'<div role="slider" ' +
314					  'aria-valuemax="{{ariaTime(API.totalTime)}}" ' +
315					  'aria-valuenow="{{ariaTime(API.currentTime)}}" ' +
316					  'aria-valuemin="0" ' +
317					  'aria-label="Time scrub bar" ' +
318					  'tabindex="0" ' +
319					  'ng-keydown="onScrubBarKeyDown($event)">' +
320				'</div>' +
321				'<div ng-class="{\'showtime\' : showTimecode && hoverTime && !cueHover}" class="timecode" ng-style="hoverTimeStyles"><span>{{hoverTime | date:"mm:ss":"+0000" }}</span></div>' +
322				'<div class="container" ng-transclude analytics-on="click" analytics-event="Seek: {{ariaTime(API.currentTime)*1000 | date:\'mm:ss\':\'+0000\'}}" analytics-category="Video: {{controller.videos[controller.currentVideo].title}}"></div>'
323			);
324		}]
325	)
326	.directive("vgScrubBar", ["VG_STATES", "VG_UTILS",
327		function (VG_STATES, VG_UTILS) {
328			return {
329				restrict: "E",
330				require: "^videogular",
331				transclude: true,
332				templateUrl: function (elem, attrs) {
333					return attrs.vgTemplate || 'vg-templates/vg-scrub-bar';
334				},
335				scope: {
336					vgThumbnails: "=",
337					cueHover: "=",
338					controller: "="
339				},
340				link: function (scope, elem, attr, API) {
341					var isSeeking = false;
342					var isPlaying = false;
343					var isPlayingWhenSeeking = false;
344					var LEFT = 37;
345					var RIGHT = 39;
346					var NUM_PERCENT = 5;
347					var thumbnailsWidth = 0;
348					var thumbWidth = 0;
349					var slider = elem[0].querySelector("div[role=slider]");
350
351					scope.thumbnails = false;
352					scope.thumbnailContainer = {};
353
354					scope.API = API;
355					scope.hoverTime = '';
356					scope.hoverTimeStyles = {};
357					scope.showTimecode = false;
358
359					scope.onLoadThumbnails = function(event) {
360						thumbnailsWidth = event.path[0].naturalWidth;
361						thumbWidth = thumbnailsWidth / 100;
362					};
363
364					scope.ariaTime = function (time) {
365						return Math.round(time / 1000);
366					};
367
368					scope.getOffset = function getOffset(event) {
369						var el = event.target,
370						x = 0;
371
372						while (el && !isNaN(el.offsetLeft)) {
373							x += el.offsetLeft - el.scrollLeft;
374							el = el.offsetParent;
375						}
376
377						return event.clientX - x;
378					};
379
380					scope.onScrubBarTouchStart = function onScrubBarTouchStart($event) {
381						var event = $event.originalEvent || $event;
382						var touches = event.touches;
383						var touchX = scope.getOffset(touches[0]);
384
385						isSeeking = true;
386						if (isPlaying) isPlayingWhenSeeking = true;
387						API.pause();
388						API.seekTime(touchX * API.mediaElement[0].duration / slider.scrollWidth);
389
390						scope.$digest();
391					};
392
393					scope.onScrubBarTouchEnd = function onScrubBarTouchEnd($event) {
394						var event = $event.originalEvent || $event;
395						if (isPlayingWhenSeeking) {
396							isPlayingWhenSeeking = false;
397							API.play();
398						}
399						isSeeking = false;
400
401						scope.$digest();
402					};
403
404					scope.onScrubBarTouchMove = function onScrubBarTouchMove($event) {
405						var event = $event.originalEvent || $event;
406						var touches = event.touches;
407						var touchX = scope.getOffset(touches[0]);
408
409						if (scope.vgThumbnails && scope.vgThumbnails.length) {
410							var second = Math.round(touchX * API.mediaElement[0].duration / slider.scrollWidth);
411							var percentage = Math.round(second * 100 / (API.totalTime / 1000));
412
413							scope.updateThumbnails(percentage);
414						}
415
416						if (isSeeking) {
417							API.seekTime(touchX * API.mediaElement[0].duration / slider.scrollWidth);
418						}
419
420						scope.$digest();
421					};
422
423					scope.onScrubBarTouchLeave = function onScrubBarTouchLeave(event) {
424						isSeeking = false;
425						scope.thumbnails = false;
426
427						scope.$digest();
428					};
429
430					scope.onScrubBarMouseDown = function onScrubBarMouseDown(event) {
431						event = VG_UTILS.fixEventOffset(event);
432
433						isSeeking = true;
434						if (isPlaying) isPlayingWhenSeeking = true;
435						API.pause();
436
437						API.seekTime(event.offsetX * API.mediaElement[0].duration / slider.scrollWidth);
438
439						scope.$digest();
440					};
441
442					scope.onScrubBarMouseUp = function onScrubBarMouseUp(event) {
443						//event = VG_UTILS.fixEventOffset(event);
444
445						if (isPlayingWhenSeeking) {
446							isPlayingWhenSeeking = false;
447							API.play();
448						}
449						isSeeking = false;
450						//API.seekTime(event.offsetX * API.mediaElement[0].duration / slider.scrollWidth);
451
452						scope.$digest();
453					};
454
455					scope.onScrubBarMouseMove = function onScrubBarMouseMove(event) {
456
457					  var second = Math.round(event.offsetX * API.mediaElement[0].duration / slider.scrollWidth);
458					  var percentage = Math.round(second * 100 / (API.totalTime / 1000));
459					  scope.hoverTime = second*1000;
460
461						var thPos = (slider.scrollWidth * percentage / 100) - (52 / 2);
462
463						if (thPos < 0) {
464							thPos = 0;
465						} else if (thPos > slider.scrollWidth - 52) {
466							// console.log(thPos + ' vs ' + slider.scrollWidth);    
467							thPos = slider.scrollWidth - 52;
468						}
469						// console.log(thPos + ' vs ' + slider.scrollWidth);   
470						
471						scope.hoverTimeStyles = {
472							"left": thPos + "px"
473						};
474						scope.showTimecode = true;
475
476						if (scope.vgThumbnails && scope.vgThumbnails.length) {
477							var second = Math.round(event.offsetX * API.mediaElement[0].duration / slider.scrollWidth);
478							var percentage = Math.round(second * 100 / (API.totalTime / 1000));
479							
480							scope.updateThumbnails(percentage);
481						}
482
483						if (isSeeking) {
484							event = VG_UTILS.fixEventOffset(event);
485							API.seekTime(event.offsetX * API.mediaElement[0].duration / slider.scrollWidth);
486						}
487
488						scope.$digest();
489					};
490
491					scope.onScrubBarMouseLeave = function onScrubBarMouseLeave(event) {
492						isSeeking = false;
493						scope.thumbnails = false;
494						scope.showTimecode = false;
495						scope.$digest();
496					};
497
498					scope.onScrubBarKeyDown = function onScrubBarKeyDown(event) {
499						var currentPercent = (API.currentTime / API.totalTime) * 100;
500
501						if (event.which === LEFT || event.keyCode === LEFT) {
502							API.seekTime(currentPercent - NUM_PERCENT, true);
503							event.preventDefault();
504						}
505						else if (event.which === RIGHT || event.keyCode === RIGHT) {
506							API.seekTime(currentPercent + NUM_PERCENT, true);
507							event.preventDefault();
508						}
509					};
510
511					scope.updateThumbnails = function updateThumbnails(percentage) {
512						var bgPos = Math.round(thumbnailsWidth * percentage / 100);
513						var thPos = (slider.scrollWidth * percentage / 100) - (thumbWidth / 2);
514
515						scope.thumbnailContainer = {
516							"width": thumbWidth + "px",
517							"left": thPos + "px"
518						};
519
520						scope.thumbnails = {
521							"background-image": 'url("' + scope.vgThumbnails + '")',
522							"background-position": -bgPos + "px 0px"
523						};
524					};
525
526					scope.setState = function setState(newState) {
527						if (!isSeeking) {
528							switch (newState) {
529								case VG_STATES.PLAY:
530									isPlaying = true;
531									break;
532
533								case VG_STATES.PAUSE:
534									isPlaying = false;
535									break;
536
537								case VG_STATES.STOP:
538									isPlaying = false;
539									break;
540							}
541						}
542					};
543
544					scope.onDestroy = function() {
545						elem.unbind("touchstart", scope.onScrubBarTouchStart);
546						elem.unbind("touchend", scope.onScrubBarTouchEnd);
547						elem.unbind("touchmove", scope.onScrubBarTouchMove);
548						elem.unbind("touchleave", scope.onScrubBarTouchLeave);
549						elem.unbind("mousedown", scope.onScrubBarMouseDown);
550						elem.unbind("mouseup", scope.onScrubBarMouseUp);
551						elem.unbind("mousemove", scope.onScrubBarMouseMove);
552						elem.unbind("mouseleave", scope.onScrubBarMouseLeave);
553					};
554
555					scope.$watch(
556						function () {
557							return API.currentState;
558						},
559						function (newVal, oldVal) {
560							if (newVal != oldVal) {
561								scope.setState(newVal);
562							}
563						}
564					);
565
566					if (scope.vgThumbnails) {
567						var thLoader = new Image();
568						thLoader.onload = scope.onLoadThumbnails.bind(scope);
569						thLoader.src = scope.vgThumbnails;
570					}
571
572					// Touch move is really buggy in Chrome for Android, maybe we could use mouse move that works ok
573					if (VG_UTILS.isMobileDevice()) {
574						elem.bind("touchstart", scope.onScrubBarTouchStart);
575						elem.bind("touchend", scope.onScrubBarTouchEnd);
576						elem.bind("touchmove", scope.onScrubBarTouchMove);
577						elem.bind("touchleave", scope.onScrubBarTouchLeave);
578					}
579					else {
580						elem.bind("mousedown", scope.onScrubBarMouseDown);
581						elem.bind("mouseup", scope.onScrubBarMouseUp);
582						// console.log(elem);
583						elem.bind("mousemove", scope.onScrubBarMouseMove);
584						elem.bind("mouseleave", scope.onScrubBarMouseLeave);
585					}
586
587					scope.$on('destroy', scope.onDestroy.bind(scope));
588				}
589			}
590		}
591	]);
592
593angular.module("com.2fdevs.videogular.plugins.controls")
594	.directive("vgTimeDisplay",
595	[function () {
596		return {
597			require: "^videogular",
598			restrict: "E",
599			link: function (scope, elem, attr, API) {
600				scope.currentTime = API.currentTime;
601				scope.timeLeft = API.timeLeft;
602				scope.totalTime = API.totalTime;
603				scope.isLive = API.isLive;
604
605				scope.$watch(
606					function () {
607						return API.currentTime;
608					},
609					function (newVal, oldVal) {
610						scope.currentTime = newVal;
611					}
612				);
613
614				scope.$watch(
615					function () {
616						return API.timeLeft;
617					},
618					function (newVal, oldVal) {
619						scope.timeLeft = newVal;
620					}
621				);
622
623				scope.$watch(
624					function () {
625						return API.totalTime;
626					},
627					function (newVal, oldVal) {
628						scope.totalTime = newVal;
629					}
630				);
631
632				scope.$watch(
633					function () {
634						return API.isLive;
635					},
636					function (newVal, oldVal) {
637						scope.isLive = newVal;
638					}
639				);
640			}
641		}
642	}]
643);
644
645angular.module("com.2fdevs.videogular.plugins.controls")
646	.run(
647		["$templateCache", function ($templateCache) {
648			$templateCache.put("vg-templates/vg-mute-button",
649				'<button type="button" class="iconButton" ng-class="muteIcon" ng-click="onClickMute()" ng-focus="onMuteButtonFocus()" ng-blur="onMuteButtonLoseFocus()" ng-mouseleave="onMuteButtonLeave()" ng-keydown="onMuteButtonKeyDown($event)" aria-label="Volume, Click to Mute"></button>');
650		}]
651	)
652	.directive("vgMuteButton",
653	[function () {
654		return {
655			restrict: "E",
656			require: "^videogular",
657			templateUrl: function (elem, attrs) {
658				return attrs.vgTemplate || 'vg-templates/vg-mute-button';
659			},
660			link: function (scope, elem, attr, API) {
661				var isMuted = false;
662				var UP = 38;
663				var DOWN = 40;
664				var CHANGE_PER_PRESS = 0.05;
665
666				scope.onClickMute = function onClickMute() {
667					if (isMuted) {
668						scope.currentVolume = scope.defaultVolume;
669					}
670					else {
671						scope.currentVolume = 0;
672						scope.muteIcon = {mute: true};
673					}
674
675					isMuted = !isMuted;
676
677					API.setVolume(scope.currentVolume);
678				};
679
680				scope.onMuteButtonFocus = function onMuteButtonFocus() {
681					scope.volumeVisibility = "visible";
682				};
683
684				scope.onMuteButtonLoseFocus = function onMuteButtonLoseFocus() {
685					scope.volumeVisibility = "hidden";
686				};
687
688				scope.onMuteButtonLeave = function onMuteButtonLeave() {
689					document.activeElement.blur();
690				};
691
692				scope.onMuteButtonKeyDown = function onMuteButtonKeyDown(event) {
693					var currentVolume = (API.volume != null) ? API.volume : 1;
694					var newVolume;
695
696					if (event.which === UP || event.keyCode === UP) {
697						newVolume = currentVolume + CHANGE_PER_PRESS;
698						if (newVolume > 1) newVolume = 1;
699
700						API.setVolume(newVolume);
701						event.preventDefault();
702					}
703					else if (event.which === DOWN || event.keyCode === DOWN) {
704						newVolume = currentVolume - CHANGE_PER_PRESS;
705						if (newVolume < 0) newVolume = 0;
706
707						API.setVolume(newVolume);
708						event.preventDefault();
709					}
710				};
711
712				scope.onSetVolume = function onSetVolume(newVolume) {
713					scope.currentVolume = newVolume;
714
715					isMuted = (scope.currentVolume === 0);
716
717					// if it's not muted we save the default volume
718					if (!isMuted) {
719						scope.defaultVolume = newVolume;
720					}
721					else {
722						// if was muted but the user changed the volume
723						if (newVolume > 0) {
724							scope.defaultVolume = newVolume;
725						}
726					}
727
728					var percentValue = Math.round(newVolume * 100);
729					if (percentValue == 0) {
730						scope.muteIcon = {mute: true};
731					}
732					else if (percentValue > 0 && percentValue < 25) {
733						scope.muteIcon = {level0: true};
734					}
735					else if (percentValue >= 25 && percentValue < 50) {
736						scope.muteIcon = {level1: true};
737					}
738					else if (percentValue >= 50 && percentValue < 75) {
739						scope.muteIcon = {level2: true};
740					}
741					else if (percentValue >= 75) {
742						scope.muteIcon = {level3: true};
743					}
744				};
745
746				scope.defaultVolume = 1;
747				scope.currentVolume = scope.defaultVolume;
748				scope.muteIcon = {level3: true};
749
750				//Update the mute button on initialization, then watch for changes
751				scope.onSetVolume(API.volume);
752				scope.$watch(
753					function () {
754						return API.volume;
755					},
756					function (newVal, oldVal) {
757						if (newVal != oldVal) {
758							scope.onSetVolume(newVal);
759						}
760					}
761				);
762			}
763		}
764	}]
765);
766
767angular.module("com.2fdevs.videogular.plugins.controls")
768	.run(
769	["$templateCache", function ($templateCache) {
770		$templateCache.put("vg-templates/vg-volume-bar",
771			'<div class="horizontalVolumeBar">\
772			  <div class="volumeBackground" ng-click="onClickVolume($event)" ng-mousedown="onMouseDownVolume()" ng-mouseup="onMouseUpVolume()" ng-mousemove="onMouseMoveVolume($event)" ng-mouseleave="onMouseLeaveVolume()" tabindex="-1">\
773				<div class="volumeValue" tabindex="-1"></div>\
774				<div class="volumeClickArea" tabindex="-1"><div class="knob"></div></div>\
775			  </div>\
776			</div>');
777	}]
778)
779	.directive("vgVolumeBar",
780	["VG_UTILS", function (VG_UTILS) {
781		return {
782			restrict: "E",
783			require: "^videogular",
784			templateUrl: function (elem, attrs) {
785				return attrs.vgTemplate || 'vg-templates/vg-volume-bar';
786			},
787			link: function (scope, elem, attr, API) {
788				var isChangingVolume = false;
789				var volumeBackElem = angular.element(elem[0].getElementsByClassName("volumeBackground"));
790				var volumeValueElem = angular.element(elem[0].getElementsByClassName("volumeValue"));
791				var knob = angular.element(elem[0].getElementsByClassName("knob"));
792
793				scope.onClickVolume = function onClickVolume(event) {
794					// console.log('here');
795					event = VG_UTILS.fixEventOffset(event);
796					var volumeWidth = parseInt(volumeBackElem.prop("offsetWidth"));
797					var value = (volumeWidth - event.offsetX) * 100 / volumeWidth;
798					var volValue = 1 - (value / 100);
799					API.setVolume(volValue);
800				};
801
802				scope.onMouseDownVolume = function onMouseDownVolume() {
803					isChangingVolume = true;
804				};
805
806				scope.onMouseUpVolume = function onMouseUpVolume() {
807					isChangingVolume = false;
808				};
809
810				scope.onMouseLeaveVolume = function onMouseLeaveVolume() {
811					isChangingVolume = false;
812				};
813
814				scope.onMouseMoveVolume = function onMouseMoveVolume(event) {
815					if (isChangingVolume) {
816						event = VG_UTILS.fixEventOffset(event);
817						var volumeWidth = parseInt(volumeBackElem.prop("offsetWidth"));
818						var value = (volumeWidth - event.offsetX) * 100 / volumeWidth;
819						var volValue = 1 - (value / 100);
820
821						API.setVolume(volValue);
822					}
823				};
824
825				scope.updateVolumeView = function updateVolumeView(value) {
826					value = value * 100;
827					volumeValueElem.css("width", (100 - value) + "%");
828					volumeValueElem.css("right", "0%");
829					knob.css("right", (100 - value - 5) + "%");
830				};
831
832				scope.onChangeVisibility = function onChangeVisibility(value) {
833					elem.css("visibility", value);
834				};
835
836				elem.css("visibility", scope.volumeVisibility);
837
838				scope.$watch("volumeVisibility", scope.onChangeVisibility);
839
840				//Update the volume bar on initialization, then watch for changes
841				scope.updateVolumeView(API.volume);
842				scope.$watch(
843					function () {
844						return API.volume;
845					},
846					function (newVal, oldVal) {
847						if (newVal != oldVal) {
848							scope.updateVolumeView(newVal);
849						}
850					}
851				);
852			}
853		}
854	}]
855);
856
857angular.module("com.2fdevs.videogular.plugins.controls")
858	.directive("vgVolume",
859	["VG_UTILS", function (VG_UTILS) {
860		return {
861			restrict: "E",
862			link: function (scope, elem, attr) {
863				scope.onMouseOverVolume = function onMouseOverVolume() {
864					scope.$evalAsync(function () {
865						scope.volumeVisibility = "visible";
866					});
867				};
868
869				scope.onMouseLeaveVolume = function onMouseLeaveVolume() {
870					scope.$evalAsync(function () {
871						scope.volumeVisibility = "hidden";
872					});
873				};
874
875				scope.onDestroy = function() {
876					elem.unbind("mouseover", scope.onScrubBarTouchStart);
877					elem.unbind("mouseleave", scope.onScrubBarTouchEnd);
878				};
879
880				// We hide volume controls on mobile devices
881				if (VG_UTILS.isMobileDevice()) {
882					elem.css("display", "none");
883				}
884				else {
885					scope.volumeVisibility = "hidden";
886
887					elem.bind("mouseover", scope.onMouseOverVolume);
888					elem.bind("mouseleave", scope.onMouseLeaveVolume);
889				}
890
891				scope.$on('destroy', scope.onDestroy.bind(scope));
892			}
893		}
894	}]
895);
896
897angular.module("com.2fdevs.videogular.plugins.controls")
898	.run(
899	["$templateCache", function ($templateCache) {
900		$templateCache.put("vg-templates/vg-captions-toggle-button",
901			'<div class="iconButton captionToggleIcon {{hasGradient}}" tabindex="0" aria-label="Toggle Captions" type="button"><div class="tooltip"><div class="captions-bridge"><div class="tt-gradient top"></div><div class="tt-gradient bottom"></div><ul><li ng-click="toggleCaption(\'off\')" ng-class="{\'active\' : activeCaption == \'off\'}">Captions off</li><li ng-class="{\'active\' : activeCaption == track.srclang}" ng-click="toggleCaption(track.srclang);" aria-label="Closed Captions: {{track.label}}" ng-repeat="track in vgTracks">{{track.label}}</li></ul></div></div></div>');
902	}]
903)
904	.directive("vgCaptionsToggle",
905	["VG_STATES", "store", "$timeout", function (VG_STATES, store, $timeout) {
906		return {
907			restrict: "E",
908			require: "^videogular",
909			scope: {
910				vgTracks: '='
911			},
912			templateUrl: function (elem, attrs) {
913				return attrs.vgTemplate || 'vg-templates/vg-captions-toggle-button';
914			},
915			link: function (scope, elem, attr, API) {
916				
917				scope.hasGradient = "";
918				$timeout(function(){
919					scope.API = API;
920					scope.activeCaption = 'off';
921					scope.previousIndex = 'off';
922					scope.v = $("vg-media video")[0];
923					if(scope.v.textTracks.length > 4) {
924						scope.hasGradient = "has-gradient"
925					}
926					// console.log(scope.v.textTracks);
927					angular.forEach(scope.v.textTracks, function(val, key) {
928						scope.v.textTracks[key].mode = "hidden";
929						// console.log(scope.v.textTracks[key].language,scope.v.textTracks[key].label);
930						if(scope.v.textTracks[key].language === 'en' && scope.v.textTracks[key].label !== 'English'){
931							if(scope.v.textTracks[key].label === 'Chinese, Simplified'){
932								scope.v.textTracks[key].language === 'cn'
933							} else if(scope.v.textTracks[key].label === 'Chinese'){
934								scope.v.textTracks[key].language === 'cn'
935							} else {
936								scope.v.textTracks[key].language === scope.v.textTracks[key].label
937							}
938						}
939						if (scope.v.textTracks[key].language == store.get("captionLang")) {
940							// console.log('found: ' + store.get("captionLang"));
941							scope.activeCaption = scope.v.textTracks[key].language;
942							scope.v.textTracks[key].mode = "showing";
943							// console.log(scope.v.textTracks[key]);
944						} 
945					});
946					console.log(scope.v.textTracks);
947					
948					if($("body").hasClass("postid-105060") || $("body").hasClass("postid-105059") || $("body").hasClass("postid-105057") || $("body").hasClass("postid-105055")){
949						scope.activeCaption = 'off';
950						var lang = 'en';
951						// store.set("captionLang", 'off');
952						angular.forEach(scope.v.textTracks, function(val, key) {
953							scope.v.textTracks[key].mode = "disabled";
954						});
955						scope.activeCaption = lang;
956						// store.set("captionLang", lang);
957
958						angular.forEach(scope.v.textTracks, function(val, key) {
959							if (scope.v.textTracks[key].language == lang) {
960								scope.v.textTracks[key].mode = "showing";
961								// console.log('showing: ' + scope.v.textTracks[key].language)
962							}
963							
964						});
965
966					
967					}
968				},1000);
969			   
970
971				scope.toggleCaption = function(lang) {
972					scope.activeCaption = 'off';
973					store.set("captionLang", 'off');
974					if(typeof(scope.v) !== "undefined" && typeof(scope.v.textTracks) !== "undefined" && scope.v.textTracks.length > 0){
975						angular.forEach(scope.v.textTracks, function(val, key) {
976							scope.v.textTracks[key].mode = "disabled";
977						});
978						if (lang !== 'off') {
979							scope.activeCaption = lang;
980							store.set("captionLang", lang);
981
982							angular.forEach(scope.v.textTracks, function(val, key) {
983								if (scope.v.textTracks[key].language == lang) {
984									scope.v.textTracks[key].mode = "showing";
985									// console.log('showing: ' + scope.v.textTracks[key].language)
986								}
987								
988							});
989
990						} else {
991
992						}
993					} else {
994						$timeout(function(){
995							scope.toggleCaption(lang);
996						},3000);
997					}
998				}
999			}
1000		}
1001	}]
1002);
1003
1004angular.module("com.2fdevs.videogular.plugins.controls")
1005.run(
1006["$templateCache", function ($templateCache) {
1007	$templateCache.put("vg-templates/vg-size-toggle-button",
1008		'<button class="iconButton sizeToggleIcon" ng-click="toggleSize()" aria-label="Toggle Size" type="button"></svg><div class="tooltip"><span> video</span></div></button>');
1009}]
1010)
1011.directive("vgSizeToggle",
1012["VG_STATES",  "store", function (VG_STATES, store) {
1013	return {
1014		restrict: "E",
1015		require: "^videogular",
1016		scope: {
1017			size: '='
1018		},
1019		templateUrl: function (elem, attrs) {
1020			return attrs.vgTemplate || 'vg-templates/vg-size-toggle-button';
1021		},
1022		link: function (scope, elem, attr, API) {
1023			scope.API = API;
1024			scope.toggleSize = function() {
1025				scope.size = !scope.size;
1026				if(store.get('sizeOverride') !== null && store.get('sizeOverride') === true){
1027					store.set('sizeOverride', false);
1028				} else {
1029					store.set('sizeOverride', true);
1030				}
1031				var vidSizeEl = angular.element(document.querySelector('.player-controller'));
1032				angular.element(vidSizeEl).toggleClass("sizeOverride");
1033			};
1034		}
1035	};
1036}]
1037);
1038
1039angular.module("com.2fdevs.videogular.plugins.controls")
1040.run(
1041["$templateCache", function ($templateCache) {
1042	$templateCache.put("vg-templates/vg-rewind-button",
1043		'<button class="iconButton rewindButton" ng-click="rewindVideo()" aria-label="Rewind Button" type="button"><div class="tooltip">Rewind 10 seconds</div></button>');
1044}]
1045)
1046.directive("vgRewindButton",
1047["VG_STATES", "$timeout", function (VG_STATES, $timeout) {
1048	return {
1049		restrict: "E",
1050		require: "^videogular",
1051		scope: {
1052			controller: '='
1053		},
1054		templateUrl: function (elem, attrs) {
1055			return attrs.vgTemplate || 'vg-templates/vg-rewind-button';
1056		},
1057		link: function (scope, elem, attr, API) {
1058			scope.API = API;
1059			scope.rewindVideo = function() {
1060				scope.API.seekTime(Math.floor(scope.API.currentTime/1000) - 10);
1061				scope.API.play();
1062			}
1063			
1064		}
1065	}
1066}]
1067);

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.