Skip to content

Instantly share code, notes, and snippets.

@luigimannoni
Created November 10, 2015 22:12
Show Gist options
  • Select an option

  • Save luigimannoni/bdab0fc10cf1ed9a2546 to your computer and use it in GitHub Desktop.

Select an option

Save luigimannoni/bdab0fc10cf1ed9a2546 to your computer and use it in GitHub Desktop.
angular-videosharing-embed.js with Youtube Iframe API support (and custom controls)
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