Closed fidoboy closed 10 years ago
Well, my idea is to replace the element first with a image spinner (while loading the thumbnail), then with the video thumbnail showing a play symbol over it. Then when user clicks on the image, replace it again with the iframe (and may be autoplay = true) to show the video. What do you think?
well, if I was you, I'd try to separate the two functionality completely. create a new directive (embedVideoThumb?), a new service to retrieve the thumbnail (based on the video ID, and have the directive call that service.
You can then decide the behavior of the click event, but that should independent from the existing directive. (the thumbnail should act as a "link" for the video directive, which could be shown in a modal dialog, could be expanded from a small thumbnail, displayed in a main element...).
About the jsonp call, I'm sure you keep it more simple, something like
in your service: getThumb: function(id) ... $http.jsonp(url).then(function(data) { return data.data }, function(error) { })
and then in the directive code run a Service.getThumb(id).then(function(data) { work with your thumbnail data here });
Yes, i'm working on it right now. I've created a new directive just as you suggested (i'm still trying how angularjs works!!). I'm having some problems cause the new regexp that you've added doesn't seems to work for youtube urls… i don't know why for the moment… i'm googling to try to find another working regexp
Ok, i've found one, here it is:
playerRegExp: /(http:\/\/|https:\/\/)(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)(\w*)(&(amp;)?[\w\?=]*)?/
Well, the problem is that I added another match to the youtube regexp (now returning 3 elements), but the directive was expecting only two :) I think i'll have to wrap the logic to match the video URL inside the service itself, and return a standard object from there.
Here is my code now:
/**
* Embed videos using AngularJS directives
* @version v0.1.8 - 2014-02-27
* @link
* @license MIT License, http://www.opensource.org/licenses/MIT
*/
angular.module('videosharing-embed', []);
angular.module('videosharing-embed').service('PlayerConfig', function() {
'use strict';
this.createInstance = function(init) {
var PlayerConfig = function(init) {
this.playerRegExp = init.playerRegExp;
this.whitelist = init.whitelist;
this.config = {
playerID: init.playerID,
thumbURL: init.thumbURL,
options: init.options
};
this.thumbURL = init.thumbURL;
this.isPlayerFromURL = function(url) {
return (url.match(this.playerRegExp) != null);
}
};
return new PlayerConfig(init);
}
});
angular.module('videosharing-embed').factory('RegisteredPlayers', ['PlayerConfig', function(PlayerConfig) {
'use strict';
var configurations = {
youtube: {
options: {
autoplay: 0,
controls: 1,
loop: 0,
},
whitelist: ['autoplay', 'controls', 'loop', 'playlist', 'rel'],
playerID: 'www.youtube.com/embed/',
thumbURL: function (id, http, q) {
var deferred = q.defer();
deferred.resolve('http://img.youtube.com/vi/'+id+'/default.jpg');
return deferred.promise;
},
protocol: 'https://',
playerRegExp: /(http:\/\/|https:\/\/)(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)(\w*)(&(amp;)?[\w\?=]*)?/
},
youtubeNoCookie: {
options: {
autoplay: 0,
controls: 1,
loop: 0,
},
whitelist: ['autoplay', 'controls', 'loop', 'playlist', 'rel'],
playerID: 'www.youtube-nocookie.com/embed/',
thumbURL: function (id, http, q) {
var deferred = q.defer();
deferred.resolve('http://img.youtube.com/vi/'+id+'/default.jpg');
return deferred.promise;
},
protocol: 'https://',
playerRegExp: /(http:\/\/|https:\/\/)www\.youtube\-nocookie\.com\/watch\?v=([A-Za-z0-9\-\_]+)/
},
vimeo: {
options: {
autoplay: 0,
loop: 0,
},
whitelist: ['autoplay', 'color', 'loop'],
playerID: 'player.vimeo.com/video/',
thumbURL: function (id, http, q) {
var deferred = $q.defer();
$http.jsonp('http://vimeo.com/api/v2/video/' + id + '.json?callback=JSON_CALLBACK').success(function (data) {
var thumbs = [];
thumbs.push(data[0]['thumbnail_small']);
thumbs.push(data[0]['thumbnail_medium']);
thumbs.push(data[0]['thumbnail_large']);
deferred.resolve(thumbs);
}).error(function () {
deferred.reject();
});
return deferred.promise;
},
protocol: 'https://',
playerRegExp: /(http:\/\/)vimeo\.com\/([A-Za-z0-9]+)/
},
dailymotion: {
options: {
autoPlay: 0,
logo: 0,
},
whitelist: ['autoPlay', 'logo', 'forceQuality'],
playerID: 'www.dailymotion.com/embed/video/',
thumbURL: function (id, http, q) {
var deferred = q.defer();
deferred.resolve('http://www.dailymotion.com/thumbnail/video/'+id);
return deferred.promise;
},
protocol: 'https://',
playerRegExp: /(http:\/\/)www\.dailymotion\.com\/video\/([A-Za-z0-9]+)/
}
};
var players = [];
angular.forEach(configurations, function(value) {
players.push(PlayerConfig.createInstance(value));
});
return players;
}]);
angular.module('videosharing-embed').directive('embedVideo', ['$filter', 'RegisteredPlayers', '$sce', function($filter, RegisteredPlayers, $sce) {
'use strict';
return {
restrict: "A",
template: '<iframe data-ng-src="{{trustedVideoSrc}}" frameborder="0"></iframe>',
scope: {},
replace: true,
link: function($scope, $element, $attrs) {
$attrs.$observe('href', function(url) {
if (url === undefined) return;
var player = null;
angular.forEach(RegisteredPlayers, function(value) {
if (value.isPlayerFromURL(url)) {
player = value;
}
});
if (player == null) return;
var videoID = url.match(player.playerRegExp)[2];
var config = player.config;
var protocol = url.match(player.playerRegExp)[1];
angular.forEach($filter('whitelist')($attrs, player.whitelist), function(value, key) {
config.options[key] = value;
});
var untrustedVideoSrc = protocol + config.playerID + videoID + $filter('videoOptions')(config.options);
$scope.trustedVideoSrc = $sce.trustAsResourceUrl(untrustedVideoSrc);
});
}
}
}]);
angular.module('videosharing-embed').directive('embedVideoThumb', ['$filter', 'RegisteredPlayers', '$sce', '$http', '$q', function($filter, RegisteredPlayers, $sce, $http, $q) {
'use strict';
return {
restrict: "A",
template: '<img />',
scope: {},
replace: true,
link: function($scope, $element, $attrs) {
$element.addClass('loading');
$attrs.$observe('href', function(url) {
if (url === undefined) return;
var player = null;
angular.forEach(RegisteredPlayers, function(value) {
if (value.isPlayerFromURL(url)) {
player = value;
}
});
if (player == null) return;
var videoID = url.match(player.playerRegExp)[2];
var config = player.config;
var protocol = url.match(player.playerRegExp)[1];
angular.forEach($filter('whitelist')($attrs, player.whitelist), function(value, key) {
config.options[key] = value;
});
var untrustedVideoSrc = protocol + config.playerID + videoID + $filter('videoOptions')(config.options);
$scope.trustedVideoSrc = $sce.trustAsResourceUrl(untrustedVideoSrc);
config.thumbURL(videoID, $http, $q).then(function (data) {
console.log('DATA: '+data);
$element.removeClass('loading');
$element.attr('src', data);
var trustedVideoSrc = $scope.trustedVideoSrc;
$element.on('click', function() {
$element[0].outerHTML = '<iframe src="'+trustedVideoSrc+'" frameborder="0"></iframe>';
});
});
});
}
}
}]);
angular.module('videosharing-embed').filter('videoOptions', function() {
'use strict';
return function(options) {
var opts = [];
angular.forEach(options, function(value, key) {
opts.push([key, value].join('='));
});
return "?" + opts.join('&');
}
});
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;
}
});
i'm sure that it can be polished and enhanced, but it works fine. Can you try it and suggest a clean an better code?
It's just a WIP but now my problem is that video options (width and height) are not being embedded into the new iframe.. any ideas?
it's because you replace the html element. if you plan to do it that way, you should also transfer the options from the source element and write those in the template you generate.
About the code: I'll still move the fetch of the thumbnail in a separate service, to avoid having $http, $q etc etc in both the directive AND the config service. In the config service, you can add a getThumbnailURL method that returns the url string. If needed (I don't think you need that for all video source), that URL can be passed to a service that does fetch the right image URL. That way, all the $http usage is kept in one place, and the directive only sees the final image URL.
That's the first thing I'd do, I'm sure it can be further improved anyway..
You are right, but i'm not sure about how to create a service to fetch the thumbnail using promises. Can you post a little example?
El 27/02/2014, a las 20:42, "erost" notifications@github.com escribió:
About the code: I'll still move the fetch of the thumbnail in a separate service, to avoid having $http, $q etc etc in both the directive AND the config service. In the config service, you can add a getThumbnailURL method that returns the url string. If needed (I don't think you need that for all video source), that URL can be passed to a service that does fetch the right image URL. That way, all the $http usage is kept in one place, and the directive only sees the final image URL.
That's the first thing I'd do, I'm sure it can be further improved anyway..
— Reply to this email directly or view it on GitHub.
small example:
angular.module('videosharing-embed').service('Thumbnail', function($http) {
return {
vimeo : function(url) {
return $http.jsonp(url).then(function(data) {
//do stuff here
//return final data
})
}
}
});
then, if you want, you can do the following in the RegisteredPlayers factory: first, pass the service into the factory...
angular.module('videosharing-embed').factory('RegisteredPlayers', ['PlayerConfig', 'Thumbnail', function(PlayerConfig, Thumbnail) {
then, when needed, use it in a getThumbURL function. Vimeo example:
vimeo: {
options: {
autoplay: 0,
loop: 0,
},
whitelist: ['autoplay', 'color', 'loop'],
playerID: 'player.vimeo.com/video/',
thumbURL: function (id) {
var url = 'http://vimeo.com/api/v2/video/' + id + '.json?callback=JSON_CALLBACK';
return Thumbnail.vimeo(url);
},
protocol: 'https://',
playerRegExp: /(http:\/\/)vimeo\.com\/([A-Za-z0-9]+)/
},
If you need to use $q because you always want to return a promise, then you should add it into the Thumbnail service.
Very clever… :) Thanks for enlighten me!
Now my new service is this:
angular.module('videosharing-embed').service('Thumbnail', function($http, $q) {
return {
straight: function (url) {
var deferred = $q.defer();
deferred.resolve(url);
return deferred.promise;
},
vimeo: function(url) {
var deferred = $q.defer();
$http.jsonp(url).success(function (data) {
deferred.resolve(data[0]['thumbnail_medium']);
}).error(function () {
deferred.reject();
});
return deferred.promise;
}
}
});
then when i call Thumbnail.straight(url) it gives me undefined error… why?
Forget it… my bad :( I forgot to add the Thumbnail param into the factory
So if this in the plugin now.
I'm trying to add a new directive into this module to allow get the video thumbnail, so i've added thumbURL into the players configurations. This thumbURL should be a function to retrieve the thumbnail URL and here is my problem, cause it returns undefined.
here is my code (for vimeo player):
but when i call:
var VideoThumb = config.thumbURL(videoID);
it throws an error and says that thumbURL is undefined. I'm sure that my code it's wrong so any help could be appreciated ;)