Created
November 10, 2015 22:12
-
-
Save luigimannoni/bdab0fc10cf1ed9a2546 to your computer and use it in GitHub Desktop.
angular-videosharing-embed.js with Youtube Iframe API support (and custom controls)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| angular.module('videosharing-embed', []); | |
| angular.module('videosharing-embed').service('PlayerConfig', function () { | |
| 'use strict'; | |
| this.createInstance = function (init) { | |
| //can use angular.copy, but I prefer this way so I can still add properties to the object | |
| var PlayerConfig = function (init) { | |
| this.type = init.type; | |
| this.playerRegExp = init.playerRegExp; | |
| this.timeRegExp = init.timeRegExp; | |
| this.whitelist = init.whitelist; | |
| this.playerID = init.playerID; | |
| this.settings = init.settings; | |
| this.transformAttrMap = init.transformAttrMap; | |
| this.processSettings = init.processSettings; | |
| this.isPlayerFromURL = function (url) { | |
| return (url.match(this.playerRegExp) != null); | |
| }, | |
| this.buildSrcURL = init.buildSrcURL, | |
| this.isAdditionalResRequired = init.isAdditionalResRequired; | |
| this.additionalRes = init.additionalRes; | |
| }; | |
| return new PlayerConfig(init); | |
| } | |
| }); | |
| // | |
| angular.module('videosharing-embed').factory('RegisteredPlayers', [ 'PlayerConfig', '$filter', '$window', function (PlayerConfig, $filter, $window) { | |
| 'use strict'; | |
| var configurations = { | |
| youtube: { | |
| type: "youtube", | |
| settings: { | |
| autoplay: 0, | |
| controls: 1, | |
| loop: 0 | |
| }, | |
| whitelist: ['autohide', 'cc_load_policy', 'color', 'disablekb', 'enablejsapi', | |
| 'autoplay', 'controls', 'loop', 'playlist', 'playsinline', 'rel', 'wmode', 'start', 'showinfo', | |
| 'end', 'fs', 'hl', 'iv_load_policy', 'list', 'listType', 'modestbranding', 'origin', | |
| 'playerapiid', 'playsinline', 'theme', 'vq'], | |
| transformAttrMap: {}, | |
| processSettings : function(settings, videoID) { | |
| if(settings['loop'] == 1 && settings['playlist'] == undefined) { | |
| console.log('%c VIDEO IS SETUP TO LOOP ====> ', 'background: #f22; color: #fff', videoID); | |
| settings['playlist'] = videoID; | |
| } | |
| return settings; | |
| }, | |
| buildSrcURL: function(protocol, videoID) { | |
| return protocol + this.playerID + videoID + $filter('videoSettings')(this.processSettings(this.settings, videoID)); | |
| }, | |
| playerID: 'www.youtube.com/embed/', | |
| playerRegExp: /([a-z\:\/]*\/\/)(?:www\.)?(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/, | |
| timeRegExp: /t=(([0-9]+)h)?(([0-9]{1,2})m)?(([0-9]+)s?)?/, | |
| isAdditionalResRequired: function() { | |
| return !$window.YT; | |
| }, | |
| additionalRes: [{id: 'ng-video-embed-youtube-api', element:'<script id="ng-video-embed-youtube-api" src="https://www.youtube.com/iframe_api" defer="defer"></script>'}] | |
| }, | |
| vimeo: { | |
| type: "vimeo", | |
| settings: { | |
| autoplay: 0, | |
| loop: 0, | |
| api: 0, | |
| player_id: '' | |
| }, | |
| whitelist: ['autoplay', 'autopause', 'badge', 'byline', 'color', 'portrait', 'loop', 'api', | |
| 'playerId', 'title'], | |
| transformAttrMap: { 'playerId' : 'player_id'}, | |
| processSettings : function(settings, videoID) { | |
| return settings; | |
| }, | |
| buildSrcURL: function(protocol, videoID) { | |
| return protocol + this.playerID + videoID + $filter('videoSettings')(this.processSettings(this.settings)); | |
| }, | |
| playerID: 'player.vimeo.com/video/', | |
| playerRegExp: /([a-z\:\/]*\/\/)(?:www\.)?vimeo\.com\/(?:channels\/[A-Za-z0-9]+\/)?([A-Za-z0-9]+)/, | |
| timeRegExp: '', | |
| isAdditionalResRequired: function() { | |
| return false; | |
| }, | |
| additionalRes: [] | |
| }, | |
| dailymotion: { | |
| type: "dailymotion", | |
| settings: { | |
| autoPlay: 0, | |
| logo: 0 | |
| }, | |
| whitelist: ['api', 'autoPlay', 'background', 'chromeless', 'controls', 'foreground', 'highlight', 'html', | |
| 'id', 'info', 'logo', 'network', 'quality', 'related', 'startscreen', 'webkit-playsinline', 'syndication'], | |
| transformAttrMap: {}, | |
| processSettings : function(settings, videoID) { | |
| return settings; | |
| }, | |
| buildSrcURL: function(protocol, videoID) { | |
| return protocol + this.playerID + videoID + $filter('videoSettings')(this.processSettings(this.settings)); | |
| }, | |
| playerID: 'www.dailymotion.com/embed/video/', | |
| playerRegExp: /([a-z\:\/]*\/\/)(?:www\.)?www\.dailymotion\.com\/video\/([A-Za-z0-9]+)/, | |
| timeRegExp: /start=([0-9]+)/, | |
| isAdditionalResRequired: function() { | |
| return false; | |
| }, | |
| additionalRes: [] | |
| }, | |
| youku: { | |
| type: "youku", | |
| settings: {}, | |
| whitelist: [], | |
| transformAttrMap: {}, | |
| processSettings : function(settings, videoID) { | |
| return settings; | |
| }, | |
| buildSrcURL: function(protocol, videoID) { | |
| return protocol + this.playerID + videoID + $filter('videoSettings')(this.processSettings(this.settings)); | |
| }, | |
| playerID: 'player.youku.com/embed/', | |
| playerRegExp: /([a-z\:\/]*\/\/)(?:www\.)?youku\.com\/v_show\/id_([A-Za-z0-9]+).html/, | |
| timeRegExp: '', | |
| isAdditionalResRequired: function() { | |
| return false; | |
| }, | |
| additionalRes: [] | |
| }, | |
| vine: { | |
| type: "vine", | |
| settings: { | |
| audio: 0, | |
| start: 0, | |
| type: 'simple' | |
| }, | |
| whitelist: ['audio','start','type'], | |
| transformAttrMap: {}, | |
| processSettings : function(settings, videoID) { | |
| return settings; | |
| }, | |
| buildSrcURL: function(protocol, videoID) { | |
| var type = this.settings['type']; | |
| return protocol + this.playerID + videoID + /embed/ + type + $filter('videoSettings')(this.processSettings(this.settings)); | |
| }, | |
| playerID: 'vine.co/v/', | |
| playerRegExp: /([a-z\:\/]*\/\/)(?:www\.)?vine\.co\/v\/([A-Za-z0-9]+)/, | |
| timeRegExp: '', | |
| isAdditionalResRequired: function() { | |
| return !$window.VINE_EMBEDS; | |
| }, | |
| additionalRes: [{id: 'ng-video-embed-vine-res-1', element:'<script id="ng-video-embed-vine-res-1" src="//platform.vine.co/static/scripts/embed.js"></script>'}] | |
| } | |
| }; | |
| var players = []; | |
| angular.forEach(configurations, function (value) { | |
| players.push(PlayerConfig.createInstance(value)); | |
| }); | |
| return players; | |
| }]); | |
| // Filters | |
| angular.module('videosharing-embed') | |
| .filter('whitelist', function () { | |
| 'use strict'; | |
| return function (options, whitelist) { | |
| var filteredOptions = {}; | |
| angular.forEach(options, function (value, key) { | |
| if (whitelist.indexOf(key) != -1) filteredOptions[key] = value; | |
| }); | |
| return filteredOptions; | |
| } | |
| }) | |
| .filter('videoSettings', function () { | |
| 'use strict'; | |
| return function (settings) { | |
| var params = []; | |
| angular.forEach(settings, function (value, key) { | |
| params.push([key, value].join('=')); | |
| }); | |
| return params.length > 0 ? "?" + params.join('&') : ""; | |
| } | |
| }); | |
| // Directive | |
| angular.module('videosharing-embed') | |
| .directive('embedVideo', [ '$filter', 'RegisteredPlayers', '$sce', '$window', '$location', '$interval', function ($filter, RegisteredPlayers, $sce, $window, $location, $interval) { | |
| 'use strict'; | |
| return { | |
| replace: true, | |
| restrict: "E", | |
| templateUrl: Drupal.settings.fiaApp.basePath + '/templates/directives/embed-video.html', | |
| scope: { | |
| href: '@', | |
| height: '@', | |
| width: '@', | |
| onChange: '&', | |
| }, | |
| link: function ($scope, $element, $attrs) { | |
| var currentHref = undefined; | |
| var playerObject; | |
| $attrs.$observe('width', function(w) { | |
| $scope.width = w; | |
| }); | |
| $attrs.$observe('height', function(h) { | |
| $scope.height = h; | |
| }); | |
| $attrs.$observe('id', function(id) { | |
| $scope.id = id; | |
| }); | |
| //handle the use of both ng-href and href | |
| $attrs.$observe('href', function(url) { | |
| if (url === undefined || url === currentHref) { | |
| return; | |
| } | |
| currentHref = url; | |
| var player = null; | |
| //search for the right player in the list of registered players | |
| angular.forEach(RegisteredPlayers, function (value) { | |
| if (value.isPlayerFromURL(url)) { | |
| player = value; | |
| } | |
| }); | |
| if (player === null) { | |
| //haven't found a match for a valid registered player | |
| $scope.onChange(); | |
| return; | |
| } | |
| var parameters = url.match(player.playerRegExp); | |
| var videoID = parameters[2], | |
| protocol = parameters[1], | |
| time = url.match(player.timeRegExp), | |
| config = player.config, | |
| currentDomain = $location.protocol() + '://' + $location.host(); | |
| if ($attrs.autoplay == 1) { | |
| $scope.thumbnail = 'http://img.youtube.com/vi/' + videoID + '/maxresdefault.jpg'; | |
| } | |
| //overwrite playback options | |
| angular.forEach($filter('whitelist')($attrs, player.whitelist), function (value, key) { | |
| var normalizedKey = player.transformAttrMap[key] != undefined ? player.transformAttrMap[key] : key; | |
| player.settings[normalizedKey] = value; | |
| }); | |
| player.settings.start = 0; | |
| if (time) { | |
| switch (player.type) { | |
| case "youtube": | |
| player.settings.start += (parseInt(time[2] || "0") * 60 * 60 ); | |
| player.settings.start += (parseInt(time[4] || "0") * 60 ); | |
| player.settings.start += (parseInt(time[6] || "0")); | |
| break; | |
| case "dailymotion": | |
| player.settings.start += (parseInt(time[1] || "0")); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| //check if there is a need to add additional resources to the page... | |
| console.debug('Debugpoint: Before checking additional resources'); | |
| if(player.isAdditionalResRequired()) { | |
| console.debug('Player needs an additional script to run...'); | |
| var body = angular.element($window.document.querySelector('body')); | |
| for(var r = 0; r < player.additionalRes.length; r++) { | |
| var res = player.additionalRes[r]; | |
| console.debug('res caught', res); | |
| if($window.document.querySelector('#'+res.id) == null) { | |
| body.append(res.element); | |
| console.debug('Adding the additional resource file:', res); | |
| } | |
| } | |
| } | |
| $scope.iframeId = $attrs.iframeId + videoID; | |
| // Call callback | |
| $scope.onChange({videoId: videoID, provider: player.type}); | |
| //build and trust the video URL | |
| var untrustedVideoSrc = player.buildSrcURL(protocol, videoID); | |
| $scope.trustedVideoSrc = $sce.trustAsResourceUrl(untrustedVideoSrc); | |
| playerObject = { | |
| id: $scope.iframeId, | |
| height: $attrs.height, | |
| width: $attrs.width, | |
| videoId: videoID | |
| }; | |
| console.log('%c PLAYER OBJECT ==== ', 'background: #f22; color: #fff', playerObject); | |
| }); | |
| $scope.$on('pause-videos', function(event, args) { | |
| console.log('pause-videos received'); | |
| if ($scope.isPaused == false) | |
| $scope.curplayer.pauseVideo(); | |
| }); | |
| $window.onYouTubeIframeAPIReady = function() { | |
| console.log('%c angular-videosharing-embed.js - YOUTUBE IS READY!!! ', 'background: #f22; color: #fff'); | |
| } | |
| $scope.onPlayerReady = function() { | |
| console.log('%c angular-videosharing-embed.js ', 'background: #f22; color: #fff', 'Iframe id ' + $scope.iframeId); | |
| // Start pooling the browser to update the video bars. | |
| var videoBarsPoolInterval = $interval(function() { | |
| var currentPos = $scope.curplayer.getCurrentTime(); // returns integer with current seconds. | |
| var maxDuration = $scope.curplayer.getDuration(); // returns integer with total seconds. | |
| var currentSeekBar = 100 * currentPos / maxDuration; | |
| var currentBufferBar = $scope.curplayer.getVideoLoadedFraction() * 100; | |
| if ($scope.curplayer.getPlayerState() == 1) { | |
| $scope.seekBarWidth = { | |
| width: currentSeekBar + '%' | |
| } | |
| $scope.bufferBarWidth = { | |
| width: currentBufferBar + '%' | |
| } | |
| } | |
| if ($scope.isMuted == false) { | |
| $scope.volumeBarWidth = { | |
| width: $scope.curplayer.getVolume() + '%' | |
| } | |
| } | |
| if ($attrs.autoplay == 1 && $scope.curplayer.getPlayerState() == 5) { | |
| $scope.curplayer.playVideo(); // Autoplay is so we need to play it | |
| // We switch the autoplay off to prevent to restart it automatically when user pauses the video. | |
| $attrs.autoplay = 0; | |
| } | |
| if ($attrs.loop == 1 && currentSeekBar >= 99) { | |
| // If we let the video reach the end we get an horrible flicking video, on 99% the video is literally finished and ready to loop again | |
| $scope.curplayer.seekTo(0); // Go back to start | |
| } | |
| // $element('.current').text( currentPos ); // Current seconds badge | |
| }, 50); | |
| } | |
| $scope.onPlayerStateChange = function() { | |
| console.log('%c angular-videosharing-embed.js ', 'background: #f22; color: #fff', 'Video state: ' + $scope.curplayer.getPlayerState()); | |
| if ($scope.curplayer.getPlayerState() == 1) { | |
| $scope.isPaused = false; | |
| } | |
| else { | |
| $scope.isPaused = true; | |
| } | |
| } | |
| // Pool the browser for Youtube initialisation. | |
| var YTinitPoolInterval = $interval(function(){ | |
| // If youtube is loaded | |
| if (!!$window.YT) { | |
| // Create context and attach the player object to this scope. | |
| console.log('%c angular-videosharing-embed.js ', 'background: #f22; color: #fff', 'attaching to #' + playerObject.id, playerObject.videoId); | |
| $scope.curplayer = new YT.Player(playerObject.id, { | |
| height: playerObject.height, | |
| width: playerObject.width, | |
| videoId: playerObject.videoId, | |
| playerVars: { | |
| 'rel': 0, | |
| 'autoplay': $attrs.autoplay, | |
| 'controls': $attrs.controls, | |
| 'color': 'white', | |
| 'wmode': 'transparent' | |
| }, | |
| events: { | |
| 'onReady': $scope.onPlayerReady, | |
| 'onStateChange': $scope.onPlayerStateChange | |
| } | |
| }); | |
| // Cancel the interval | |
| $interval.cancel(YTinitPoolInterval); | |
| YTinitPoolInterval = undefined; | |
| // Initial states | |
| $scope.isPaused = true; | |
| $scope.isMuted = false; | |
| } | |
| }, 1000); | |
| $scope.embedVideoPlay = function(){ | |
| console.log('%c angular-videosharing-embed.js ', 'background: #222; color: #bada55', 'Play Button clicked'); | |
| if ($scope.isPaused) { | |
| $scope.curplayer.playVideo(); | |
| } | |
| else { | |
| $scope.curplayer.pauseVideo(); | |
| } | |
| } | |
| $scope.embedVideoSeekStart = function($event){ | |
| console.log('%c angular-videosharing-embed.js ', 'background: #222; color: #bada55', 'Seek bar click start', $event); | |
| $scope.timeDragging = true; | |
| var maxduration = $scope.curplayer.getDuration(); | |
| var position = $event.offsetX - 11; | |
| var percentage = 100 * position / ($event.currentTarget.clientWidth - 22); | |
| if(percentage > 100) { | |
| percentage = 100; | |
| } | |
| if(percentage < 0) { | |
| percentage = 0; | |
| } | |
| $scope.seekBarWidth = { | |
| width: percentage + '%' | |
| } | |
| if ($scope.curplayer.getPlayerState() == 1) { | |
| $scope.playOnRelease = true; | |
| } | |
| else { | |
| $scope.playOnRelease = false; | |
| } | |
| $scope.curplayer.pauseVideo(); | |
| $scope.curplayer.seekTo(maxduration * percentage / 100, true); | |
| } | |
| $scope.embedVideoSeekMove = function($event){ | |
| //console.log('%c angular-videosharing-embed.js ', 'background: #222; color: #bada55', 'Seek bar mouse move', $event.offsetX); | |
| // If user is dragging | |
| if ($scope.timeDragging == true) { | |
| var maxduration = $scope.curplayer.getDuration(); | |
| var position = $event.offsetX - 11; | |
| var percentage = 100 * position / ($event.currentTarget.clientWidth - 22); | |
| if(percentage > 100) { | |
| percentage = 100; | |
| } | |
| if(percentage < 0) { | |
| percentage = 0; | |
| } | |
| $scope.seekBarWidth = { | |
| width: percentage + '%' | |
| } | |
| $scope.curplayer.seekTo(maxduration * percentage / 100, true); | |
| } | |
| } | |
| $scope.embedVideoSeekRelease = function($event){ | |
| console.log('%c angular-videosharing-embed.js ', 'background: #222; color: #bada55', 'Seek bar click release', $event); | |
| $scope.timeDragging = false; | |
| if ($scope.playOnRelease == true) { | |
| $scope.curplayer.playVideo(); | |
| } | |
| } | |
| $scope.embedVideoMute = function(){ | |
| console.log('%c angular-videosharing-embed.js ', 'background: #222; color: #bada55', 'Mute Button clicked'); | |
| if ( $scope.curplayer.isMuted() ) { | |
| $scope.isMuted = false; | |
| $scope.volumeBarWidth = { | |
| width: $scope.curplayer.getVolume() + '%' | |
| } | |
| $scope.curplayer.unMute(); | |
| } | |
| else { | |
| $scope.isMuted = true; | |
| $scope.volumeBarWidth = { | |
| width: '0%' | |
| } | |
| $scope.curplayer.mute(); | |
| } | |
| } | |
| $scope.embedVideoVolumeStart = function($event){ | |
| console.log('%c angular-videosharing-embed.js ', 'background: #222; color: #bada55', 'Volume bar click start', $event); | |
| $scope.volumeDragging = true; | |
| var position = $event.offsetX - 11; | |
| var percentage = 100 * position / ($event.currentTarget.clientWidth - 22); | |
| if(percentage > 100) { | |
| percentage = 100; | |
| } | |
| if(percentage < 0) { | |
| percentage = 0; | |
| } | |
| $scope.volumeBarWidth = { | |
| width: percentage + '%' | |
| } | |
| $scope.curplayer.setVolume(percentage); | |
| } | |
| $scope.embedVideoVolumeMove = function($event){ | |
| //console.log('%c angular-videosharing-embed.js ', 'background: #222; color: #bada55', 'Volume bar mouse move', $event.offsetX); | |
| // If user is dragging | |
| if ($scope.volumeDragging == true) { | |
| var position = $event.offsetX - 11; | |
| var percentage = 100 * position / ($event.currentTarget.clientWidth - 22); | |
| if(percentage > 100) { | |
| percentage = 100; | |
| } | |
| if(percentage < 0) { | |
| percentage = 0; | |
| } | |
| $scope.volumeBarWidth = { | |
| width: percentage + '%' | |
| } | |
| $scope.curplayer.setVolume(percentage); | |
| } | |
| } | |
| $scope.embedVideoVolumeRelease = function($event){ | |
| console.log('%c angular-videosharing-embed.js ', 'background: #222; color: #bada55', 'Volume bar click release', $event); | |
| $scope.volumeDragging = false; | |
| } | |
| $scope.embedVideoFullscreen = function(){ | |
| console.log('%c angular-videosharing-embed.js ', 'background: #222; color: #bada55', 'Fullscreen Button clicked'); | |
| var iframeElement = document.getElementById($attrs.iframeId); | |
| if (iframeElement.mozRequestFullScreen) { | |
| iframeElement.mozRequestFullScreen(); | |
| } else if (iframeElement.webkitRequestFullScreen) { | |
| iframeElement.webkitRequestFullScreen(); | |
| } | |
| } | |
| $element.on('$destroy', function(){ | |
| if (typeof(YTinitPoolInterval) != 'undefined') { | |
| $interval.cancel(YTinitPoolInterval); | |
| YTinitPoolInterval = undefined; | |
| } | |
| if (typeof(videoBarsPoolInterval) != 'undefined') { | |
| $interval.cancel(videoBarsPoolInterval); | |
| videoBarsPoolInterval = undefined; | |
| } | |
| }); | |
| } | |
| } | |
| }]); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment