brandly / brandly/angular-youtube-embed
routechange breaks yt player
- Dominant language
- HTML
- Stars
- 505
- Forks
- 145
- PR merge metrics
- No merged PRs in 30d
Description
I use routes, when I have a video on a page the server breaks.. (the ]*>|<\/a>))[?=&+%\w.-]*/ig;
var timeRegexp = /t=(\d+)[ms]?(\d+)?s?/;
function contains(str, substr) {
return (str.indexOf(substr) > -1);
}
Service.getIdFromURL = function getIdFromURL(url) {
var id = url.replace(youtubeRegexp, '$1');
if (contains(id, ';')) {
var pieces = id.split(';');
if (contains(pieces[1], '%')) {
// links like this:
// "http://www.youtube.com/attribution_link?a=pxa6goHqzaA&u=%2Fwatch%3Fv%3DdPdgx30w9sU%26feature%3Dshare"
// have the real query string URI encoded behind a ';'.
// at this point, `id is 'pxa6goHqzaA;u=%2Fwatch%3Fv%3DdPdgx30w9sU%26feature%3Dshare'
var uriComponent = decodeURIComponent(pieces[1]);
id = ('https://youtube.com?&enablejsapi=1&origin=http://localhost' + uriComponent)
.replace(youtubeRegexp, '$1');
} else {
// https://www.youtube.com/watch?v=VbNF9X1waSc&feature=youtu.be
// `id` looks like 'VbNF9X1waSc;feature=youtu.be' currently.
// strip the ';feature=youtu.be'
id = pieces[0];
}
} else if (contains(id, '#')) {
// id might look like '93LvTKF_jW0#t=1'
// and we want '93LvTKF_jW0'
id = id.split('#')[0];
}
return id;
};
Service.getTimeFromURL = function getTimeFromURL(url) {
url = url || '';
// t=4m20s
// returns ['t=4m20s', '4', '20']
// t=46s
// returns ['t=46s', '46']
// t=46
// returns ['t=46', '46']
var times = url.match(timeRegexp);
if (!times) {
// zero seconds
return 0;
}
// assume the first
var full = times[0],
minutes = times[1],
seconds = times[2];
// t=4m20s
if (typeof seconds !== 'undefined') {
seconds = parseInt(seconds, 10);
minutes = parseInt(minutes, 10);
// t=4m
} else if (contains(full, 'm')) {
minutes = parseInt(minutes, 10);
seconds = 0;
// t=4s
// t=4
} else {
seconds = parseInt(minutes, 10);
minutes = 0;
}
// in seconds
return seconds + (minutes * 60);
};
Service.ready = false;
function applyServiceIsReady() {
$rootScope.$apply(function () {
Service.ready = true;
});
};
// If the library isn't here at all,
if (typeof YT === "undefined") {
// ...grab on to global callback, in case it's eventually loaded
$window.onYouTubeIframeAPIReady = applyServiceIsReady;
console.log('Unable to find YouTube iframe library on this page.')
} else if (YT.loaded) {
Service.ready = true;
} else {
YT.ready(applyServiceIsReady);
}
```
return Service;
});
return Service;
}])
.directive('youtubeVideo', ['$window', 'youtubeEmbedUtils', '$rootScope', function ($window, youtubeEmbedUtils, $rootScope) {
var uniqId = 1;
```
// from YT.PlayerState
var stateNames = {
'-1': 'unstarted',
0: 'ended',
1: 'playing',
2: 'paused',
3: 'buffering',
5: 'queued'
};
var eventPrefix = 'youtube.player.';
$window.YTConfig = {
host: 'https://www.youtube.com'
};
return {
restrict: 'EA',
scope: {
videoId: '=?',
videoUrl: '=?',
player: '=?',
playerVars: '=?',
playerHeight: '=?',
playerWidth: '=?'
},
link: function (scope, element, attrs) {
// allows us to $watch `ready`
//element.attr('player', scope.videoId);
scope.utils = youtubeEmbedUtils;
// player-id attr > id attr > directive-generated ID
//element[0].attr = ('id', scope.vId);
var playerId = attrs.playerId || element[0].id || 'u' + uniqId++;
var videoId = scope.videoId;
element[0].id = videoId;
element[0].vid = videoId;
// Attach to element
scope.playerHeight = scope.playerHeight || 390;
scope.playerWidth = scope.playerWidth || 640;
scope.playerVars = scope.playerVars || {};
// YT calls callbacks outside of digest cycle
function applyBroadcast () {
var args = Array.prototype.slice.call(arguments);
scope.$apply(function () {
scope.$emit.apply(scope, args);
});
}
function onPlayerStateChange (event) {
var state = stateNames[event.data];
if (typeof state !== 'undefined') {
applyBroadcast(eventPrefix + state, scope.player, event);
}
scope.$apply(function () {
scope.player.currentState = state;
});
}
function onPlayerReady (event) {
applyBroadcast(eventPrefix + 'ready', scope.player, event);
}
function onPlayerError (event) {
applyBroadcast(eventPrefix + 'error', scope.player, event);
}
```
/*
function createPlayer () {
var playerVars = angular.copy(scope.playerVars);
playerVars.start = playerVars.start || scope.urlStartTime;
var player = new YT.Player(videoId, {
height: scope.playerHeight,
width: scope.playerWidth,
videoId: scope.videoId,
player: scope.videoId,
playerVars: playerVars,
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange,
onError: onPlayerError
}
});
```
player.id = scope.videoId;
//player.player = scope.videoId;
return player;
}*/
function createPlayer () {
var playerVars = angular.copy(scope.playerVars);
playerVars.start = playerVars.start || scope.urlStartTime;
var player = new YT.Player(videoId, {
height: scope.playerHeight,
width: scope.playerWidth,
videoId: scope.videoId,
playerVars: playerVars,
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange
}
});
player.id = scope.videoId;
// I don't know why, but in some cases player.d is missing.
// It is needed in case it needs to be destroyed.
//So if it is not there, then I just arbitrarily set it to true.
if (player.d) {
console.log("YouTube player.d exists in this object: ");
console.log(player);
} else {
player.d = true;
}
return player;
}
function loadPlayer () {
if (scope.videoId || scope.playerVars.list) {
if (scope.player && typeof scope.player.destroy === 'function') {
scope.player.destroy();
}
$rootScope.$watch('$stateChangeSuccess', function() {
scope.player = createPlayer();
});
}
};
var stopWatchingReady = scope.$watch(
function () {
return scope.utils.ready
// Wait until one of them is defined...
&& (typeof scope.videoUrl !== 'undefined'
|| typeof scope.videoId !== 'undefined'
|| typeof scope.playerVars.list !== 'undefined');
},
function (ready) {
if (ready) {
stopWatchingReady();
// URL takes first priority
if (typeof scope.videoUrl !== 'undefined') {
scope.$watch('videoUrl', function (url) {
scope.videoId = scope.utils.getIdFromURL(url);
scope.urlStartTime = scope.utils.getTimeFromURL(url);
loadPlayer();
});
// then, a video ID
} else if (typeof scope.videoId !== 'undefined') {
scope.$watch('videoId', function () {
scope.urlStartTime = null;
loadPlayer();
});
// finally, a list
} else {
scope.$watch('playerVars.list', function () {
scope.urlStartTime = null;
loadPlayer();
});
}
}
});
scope.$watchCollection(['playerHeight', 'playerWidth'], function() {
if (scope.player) {
//scope.player.setSize(scope.playerWidth, scope.playerHeight);
}
});
scope.$on('$destroy', function () {
scope.player && scope.player.destroy();
});
}
};
```
}]);`
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the youtubeVideo directive and youtubeEmbedUtils service shown in the issue, focusing on the $stateChangeSuccess handling and player lifecycle. Reproduce the failure by navigating between routed pages containing a video, then verify that YouTube elements are converted and the player remains usable after route changes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- angularjs, javascript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 20/100