erost / ng-videosharing-embed

embed youtube/vimeo/dailymotion videos using AngularJS directives
MIT License
97 stars 44 forks source link

Trying to enhance this module #8

Closed fidoboy closed 10 years ago

fidoboy commented 10 years ago

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):

thumbURL: function (id) {
    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;
},

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 ;)

fidoboy commented 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?

erost commented 10 years ago

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 });

fidoboy commented 10 years ago

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

fidoboy commented 10 years ago

Ok, i've found one, here it is:

playerRegExp: /(http:\/\/|https:\/\/)(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)(\w*)(&(amp;)?[\w\?=]*)?/
erost commented 10 years ago

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.

fidoboy commented 10 years ago

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?

fidoboy commented 10 years ago

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?

erost commented 10 years ago

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.

erost commented 10 years ago

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..

fidoboy commented 10 years ago

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.

erost commented 10 years ago

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.

fidoboy commented 10 years ago

Very clever… :) Thanks for enlighten me!

fidoboy commented 10 years ago

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?

fidoboy commented 10 years ago

Forget it… my bad :( I forgot to add the Thumbnail param into the factory

dsmithco commented 9 years ago

So if this in the plugin now.