Created by @_Shakeel and others.
@@ -125,6 +151,10 @@
Sponsored by:
"url": "https://zenplayer.audio",
"logo": "https://zenplayer.audio/img/zen-audio-player-905.png"
}
-
+
+
+
+
+
diff --git a/js/everything.js b/js/everything.js
index c82e9a78..95deb3f9 100644
--- a/js/everything.js
+++ b/js/everything.js
@@ -1,5 +1,4 @@
-/* global URI getSearchResults, getAutocompleteSuggestions, parseYoutubeVideoID, getYouTubeVideoDescription */
-
+/* global URI getSearchResults, getAutocompleteSuggestions, parseYoutubeVideoID, getYouTubeVideoDescription, gapi */
// Pointer to Keen client
var client;
/**
@@ -59,10 +58,10 @@ var youTubeDataApiKey = "AIzaSyCxVxsC5k46b8I-CLXlF3cZHjpiqP_myVk";
var currentVideoID;
var errorMessage = {
- init: function() {
+ init: function () {
// nothing for now
},
- show: function(message) {
+ show: function (message) {
$("#zen-error").text("ERROR: " + message);
$("#zen-error").show();
@@ -74,9 +73,11 @@ var errorMessage = {
// Send the error to Google Analytics
ga("send", "event", "error", message);
- sendKeenEvent("error", {"message": message});
+ sendKeenEvent("error", {
+ "message": message
+ });
},
- hide: function() {
+ hide: function () {
$("#zen-error").text("").hide();
ZenPlayer.show();
}
@@ -122,7 +123,11 @@ function handleYouTubeError(details) {
// Update the UI w/ error
errorMessage.show(message);
ga("send", "event", "YouTube iframe API error", verboseMessage);
- sendKeenEvent("YouTube iframe API error", {verbose: verboseMessage, message: message, code: details.code});
+ sendKeenEvent("YouTube iframe API error", {
+ verbose: verboseMessage,
+ message: message,
+ code: details.code
+ });
// Log debug info
console.log("Verbose debug error message: ", verboseMessage);
@@ -133,12 +138,11 @@ function handleYouTubeError(details) {
var ZenPlayer = {
updated: false,
isPlaying: false,
- init: function(videoID) {
+ init: function (videoID) {
// Inject svg with control icons
$("#plyr-svg").load("../bower_components/plyr/dist/plyr.svg");
plyrPlayer = document.querySelector(".plyr");
-
plyr.setup(plyrPlayer, {
autoplay: true,
controls: ["play", "progress", "current-time", "duration", "mute", "volume"],
@@ -148,19 +152,18 @@ var ZenPlayer = {
// Load video into Plyr player
if (plyrPlayer.plyr) {
var that = this;
- plyrPlayer.addEventListener("error", function(event) {
+ plyrPlayer.addEventListener("error", function (event) {
if (event && event.detail && typeof event.detail.code === "number") {
handleYouTubeError(event.detail);
ZenPlayer.hide();
}
});
- plyrPlayer.addEventListener("ready", function() {
+ plyrPlayer.addEventListener("ready", function () {
// Noop if we have nothing to play
if (!currentVideoID || currentVideoID.length === 0) {
return;
}
-
// Gather video info
that.videoTitle = plyrPlayer.plyr.embed.getVideoData().title;
that.videoAuthor = plyrPlayer.plyr.embed.getVideoData().author;
@@ -180,9 +183,11 @@ var ZenPlayer = {
that.setupTitle();
that.setupVideoDescription(videoID);
that.setupPlyrToggle();
+
+ // Add the video to the playlist if not already in list
});
- plyrPlayer.addEventListener("playing", function() {
+ plyrPlayer.addEventListener("playing", function () {
if (that.updated) {
return;
}
@@ -215,7 +220,7 @@ var ZenPlayer = {
updateTweetMessage();
});
- plyrPlayer.addEventListener("timeupdate", function() {
+ plyrPlayer.addEventListener("timeupdate", function () {
// Store the current time of the video.
var resumeTime = 0;
if (window.sessionStorage) {
@@ -243,31 +248,33 @@ var ZenPlayer = {
$("#zen-video-title").attr("href", updatedUrl);
});
- plyrPlayer.addEventListener("playing", function() {
+ plyrPlayer.addEventListener("playing", function () {
this.isPlaying = true;
}.bind(this));
- plyrPlayer.addEventListener("pause", function() {
+ plyrPlayer.addEventListener("pause", function () {
this.isPlaying = false;
}.bind(this));
+
plyrPlayer.plyr.source({
type: "video",
title: "Title",
- sources: [{
- src: currentVideoID,
- type: "youtube"
- }]
+ sources: [
+ {
+ src: currentVideoID,
+ type: "youtube"
+ }]
});
}
},
- show: function() {
+ show: function () {
$("#audioplayer").show();
},
- hide: function() {
+ hide: function () {
$("#audioplayer").hide();
},
- setupTitle: function() {
+ setupTitle: function () {
// Prepend music note only if title does not already begin with one.
var tmpVideoTitle = this.videoTitle;
if (!/^[\u2669\u266A\u266B\u266C\u266D\u266E\u266F]/.test(tmpVideoTitle)) {
@@ -276,23 +283,23 @@ var ZenPlayer = {
$("#zen-video-title").html(tmpVideoTitle);
$("#zen-video-title").attr("href", this.videoUrl);
},
- setupVideoDescription: function(videoID) {
+ setupVideoDescription: function (videoID) {
var description = anchorURLs(this.videoDescription);
description = anchorTimestamps(description, videoID);
$("#zen-video-description").html(description);
$("#zen-video-description").hide();
- $("#toggleDescription").click(function(event) {
+ $("#toggleDescription").click(function (event) {
toggleElement(event, "#zen-video-description", "Description");
});
},
- setupPlyrToggle: function() {
+ setupPlyrToggle: function () {
// Show player button click event
- $("#togglePlayer").click(function(event) {
+ $("#togglePlayer").click(function (event) {
toggleElement(event, ".plyr__video-wrapper", "Player");
});
},
- getVideoDescription: function(videoID) {
+ getVideoDescription: function (videoID) {
var description = "";
if (isFileProtocol()) {
@@ -303,7 +310,7 @@ var ZenPlayer = {
getYouTubeVideoDescription(
videoID,
youTubeDataApiKey,
- function(data) {
+ function (data) {
if (data.items.length === 0) {
errorMessage.show("Video description not found");
}
@@ -311,7 +318,7 @@ var ZenPlayer = {
description = data.items[0].snippet.description;
}
},
- function(jqXHR, textStatus, errorThrown) {
+ function (jqXHR, textStatus, errorThrown) {
logError(jqXHR, textStatus, errorThrown, "Video Description error");
}
);
@@ -324,10 +331,10 @@ var ZenPlayer = {
return description;
},
- play: function() {
+ play: function () {
plyrPlayer.plyr.embed.playVideo();
},
- pause: function() {
+ pause: function () {
plyrPlayer.plyr.embed.pauseVideo();
}
};
@@ -459,10 +466,10 @@ function makeSearchURL(searchQuery) {
function anchorURLs(text) {
/* RegEx to match http or https addresses
- * This will currently only match TLD of two or three letters
- * Ends capture when:
- * (1) it encounters a TLD
- * (2) it encounters a period (.) or whitespace, if the TLD was followed by a forwardslash (/) */
+ * This will currently only match TLD of two or three letters
+ * Ends capture when:
+ * (1) it encounters a TLD
+ * (2) it encounters a period (.) or whitespace, if the TLD was followed by a forwardslash (/) */
var re = /((?:http|https)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(?:\/\S*[^\.\s])?)/g;
/* Wraps all found URLs in
tags */
return text.replace(re, "$1");
@@ -481,7 +488,7 @@ function anchorTimestamps(text, videoID) {
(?:$|\:[0-5]\d)) either the string ends or is a a number between 00-59
*/
var re = /((?:[0-5]\d|\d|)(?:\d|\:[0-5]\d)(?:$|\:[0-5]\d))/g;
- return text.replace(re, function(match) {
+ return text.replace(re, function (match) {
return "
" + match + "";
});
}
@@ -526,14 +533,14 @@ var demos = [
"koJv-j1usoI", // The Glitch Mob - Starve the Ego, Feed the Soul
"EBerFisqduk", // Cazzette - Together (Lost Kings Remix)
"jxKjOOR9sPU", // The Temper Trap - Sweet Disposition
- "03O2yKUgrKw" // Mike Mago & Dragonette - Outlines
+ "03O2yKUgrKw" // Mike Mago & Dragonette - Outlines
];
function pickDemo() {
return demos[Math.floor(Math.random() * demos.length)];
}
-$(function() {
+$(function () {
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
$("#container").hide();
$("#mobile-message").html("Sorry, we don't support mobile devices.");
@@ -563,7 +570,7 @@ $(function() {
getSearchResults(
currentSearchQuery,
youTubeDataApiKey,
- function(data) {
+ function (data) {
if (data.pageInfo.totalResults === 0) {
errorMessage.show("No results.");
return;
@@ -577,7 +584,7 @@ $(function() {
$("#search-results ul").append(start + result.id.videoId + ">" + result.snippet.title + "
");
});
},
- function(jqXHR, textStatus, errorThrown) {
+ function (jqXHR, textStatus, errorThrown) {
logError(jqXHR, textStatus, errorThrown, "Search error");
}
);
@@ -591,18 +598,18 @@ $(function() {
minLength: 1
}, {
source: function (query, processSync, processAsync) {
- getAutocompleteSuggestions(query, function(data) {
- return processAsync($.map(data[1], function(item) {
+ getAutocompleteSuggestions(query, function (data) {
+ return processAsync($.map(data[1], function (item) {
return item[0];
}));
});
}
- }).bind("typeahead:selected", function(obj, datum) {
+ }).bind("typeahead:selected", function (obj, datum) {
window.location.href = makeSearchURL(datum);
});
// Handle form submission
- $("#form").submit(function(event) {
+ $("#form").submit(function (event) {
event.preventDefault();
var formValue = $.trim($("#v").val());
var formValueTime = /&t=(\d*)$/g.exec(formValue);
@@ -613,7 +620,9 @@ $(function() {
if (formValue) {
var videoID = wrapParseYouTubeVideoID(formValue, true);
ga("send", "event", "form submitted", videoID);
- sendKeenEvent("Form submitted", {videoID: videoID});
+ sendKeenEvent("Form submitted", {
+ videoID: videoID
+ });
if (isFileProtocol()) {
errorMessage.show("Skipping video lookup request as we're running the site locally.");
}
@@ -628,7 +637,7 @@ $(function() {
fields: "items/snippet/description",
id: videoID
},
- success: function(data) {
+ success: function (data) {
if (data.items.length === 0) {
window.location.href = makeSearchURL(formValue);
}
@@ -636,7 +645,7 @@ $(function() {
window.location.href = makeListenURL(videoID, formValueTime);
}
}
- }).fail(function(jqXHR, textStatus, errorThrown) {
+ }).fail(function (jqXHR, textStatus, errorThrown) {
logError(jqXHR, textStatus, errorThrown, "Lookup error");
});
}
@@ -652,10 +661,12 @@ $(function() {
}
// Handle demo link click
- $("#demo").click(function(event) {
+ $("#demo").click(function (event) {
event.preventDefault();
ga("send", "event", "demo", "clicked");
- sendKeenEvent("Demo", {action: "clicked"});
+ sendKeenEvent("Demo", {
+ action: "clicked"
+ });
// Don't continue appending to the URL if it appears "good enough".
// This is likely only a problem if the demo link didn't work right the first time
@@ -665,14 +676,16 @@ $(function() {
}
else {
ga("send", "event", "demo", "already had video ID in URL");
- sendKeenEvent("demo", {action: "already had video ID in URL"});
+ sendKeenEvent("demo", {
+ action: "already had video ID in URL"
+ });
}
});
// Load the player
ZenPlayer.init(currentVideoID);
- $(document).on("keyup", function(evt) {
+ $(document).on("keyup", function (evt) {
// 32 = spacebar, toggle play/pause if not typing in the search box
if (evt.keyCode === 32 && !$("#v").is(":focus")) {
evt.preventDefault();
@@ -685,20 +698,282 @@ $(function() {
}
});
- $(document).on("keydown", function(evt) {
+ $(document).on("keydown", function (evt) {
// 32 = spacebar, if not typing in the search prevent "page down" scrolling
if (evt.keyCode === 32 && !$("#v").is(":focus")) {
evt.preventDefault();
}
});
});
+// Youtube Authentication and Playlist code
+var OAUTH2_CLIENT_ID = "763547295554-ubbn5qqth37ov4j11a2jt8mjl95eqm2l.apps.googleusercontent.com"; // ToDo: use another client id
+var OAUTH2_SCOPES = "https://www.googleapis.com/auth/youtube";
+var tmpYouTubeApiKey = "AIzaSyBvkfhXrqTMk8WJuhN4CeRrIg4BUm5Md0E"; // ToDo: assign to youTubeDataApiKey
+var YOUTUBE_PLAYLIST_NAME = "Zen Audio Player";
+var LOCALSTORAGE_PLAYLIST_ID_KEY = "zap_playlist_id";
+var youtubeVideos;
+/**
+ * This function will be called after loading the page
+ */
+/* eslint-disable no-unused-vars */
+function handleClientLoad() {
+ // Load the API client and auth library
+ if (!isFileProtocol()) {
+ gapi.load("client:auth2", checkAuth);
+ }
+}
+/* eslint-enable no-unused-vars */
+
+/**
+ * This function checks the authentication using YouTube API, exactly after the page loads
+ */
+function checkAuth() {
+ /* eslint camelcase: [2, {properties: "never"}] */
+ gapi.client.setApiKey(tmpYouTubeApiKey);
+ gapi.auth.authorize({
+ client_id: OAUTH2_CLIENT_ID,
+ scope: OAUTH2_SCOPES,
+ immediate: true
+ }, handleAuthResult);
+}
+
+/**
+ * This function handles the result of the authentication call
+ * @param {object} authResult authentication result - Generated by library
+ */
+function handleAuthResult(authResult) {
+ if (authResult && !authResult.error) {
+ // Authorization was successful. Hide authorization prompts and show
+ // content that should be visible after authorization succeeds.
+ changeSignInButtonVisibility(true);
+ makeApiCall();
+ }
+ else {
+ changeSignInButtonVisibility(false);
+ // Make the #login-link clickable. Attempt a non-immediate OAuth 2.0
+ // client flow. The current function is called when that flow completes.
+ $("#youtube-login").click(function () {
+ gapi.auth.authorize({
+ client_id: OAUTH2_CLIENT_ID,
+ scope: OAUTH2_SCOPES,
+ immediate: false
+ }, handleAuthResult);
+ });
+ }
+}
+
+/**
+ * This function calls the youtube API v3 and then execute the requests (withing handleAPILoaded function) after a successful load
+ */
+function makeApiCall() {
+ gapi.client.load("youtube", "v3", function () {
+ handleAPILoaded();
+ });
+}
+/**
+ * This function gets user videos from Zap playlist and store them into array
+ * It also checks if the current playing video isn't in the Youtube playlist, it calls the fucntion which adds it
+ */
+function getUserVideosFromZapPlaylist() {
+ if (localStorage.getItem(LOCALSTORAGE_PLAYLIST_ID_KEY) && !youtubeVideos && !getCurrentSearchQuery()) {
+ var request = gapi.client.youtube.playlistItems.list({
+ part: "snippet",
+ mine: true,
+ maxResults: 50,
+ playlistId: localStorage.getItem(LOCALSTORAGE_PLAYLIST_ID_KEY)
+ });
+ request.execute(function (response) {
+ if (response.code === 404) {
+ // playlist not found
+ checkIfUserAlreadyHasZapYoutubePlaylist();
+ }
+ else {
+ youtubeVideos = response.items;
+ if (youtubeVideos.length > 0) {
+ // Check if current played video not in playlist add it
+ if (getCurrentVideoID()) {
+ var doesTheVideoExist = false;
+ for (var i = 0; i < youtubeVideos.length; i++) {
+ if (youtubeVideos[i].snippet.resourceId.videoId === currentVideoID) {
+ doesTheVideoExist = true;
+ break;
+ }
+ }
+ if (!doesTheVideoExist) {
+ addVideoToPlaylist(currentVideoID);
+ }
+ }
+ // Add videos to queue
+ fetchYoutubeVideosIntoQueue();
+ }
+ }
+ });
+ }
+}
+
+
+/**
+ * This function uses the filled youtubeVideos array and fills the UI videos queue
+ */
+function fetchYoutubeVideosIntoQueue() {
+ var i = 0;
+ youtubeVideos.forEach(function (video) {
+ var $div = $("
", {
+ "class": "queue__vid"
+ });
+ $div.append($("
" + video.snippet.title + ""));
+ $div.append($("
"));
+ $div.append($("
"));
+
+ $div.click(function () {
+ window.location.href = makeListenURL(video.snippet.resourceId.videoId, 0);
+ });
+ $(".queue").append($div);
+ i++;
+ });
+ $(".queue__vid__cancel").click(function (e) {
+ if (confirm("Do you want to delete it for sure ?")) {
+ youtubeVideos.splice($(this).data("vidindex"), 1);
+ $(this).parent().hide();
+ if (youtubeVideos.length === 0) {
+ $(".queue").hide();
+ }
+ removeYoutubeVideoFromPlaylist($(this).data("vidid"));
+ }
+ e.stopPropagation();
+ return true;
+ });
+ $(".queue").css("visibility", "visible");
+}
+/**
+ * This function contains the requests that will be executed after the load is completed
+ * IF the localStorage doesn't contain zap_playlist_id: create the playlist in the user YouTube account and then store the zap_playlist_id
+ * IF the current page displays video: check if it doesn't exist in the play list then add it
+ */
+function handleAPILoaded() {
+ // if not video is currently playing
+ if (!getCurrentVideoID() && !localStorage.getItem(LOCALSTORAGE_PLAYLIST_ID_KEY)) {
+ checkIfUserAlreadyHasZapYoutubePlaylist();
+ }
+ // Get user videos and store them into array
+ getUserVideosFromZapPlaylist();
+ if (getCurrentSearchQuery()) {
+ $(".queue").hide(); // Hide queue in case of search keyword exists
+ }
+}
+/**
+ * If the user for some reason cleared the browser localstorage, we don't want to re-create another playlist. So we will list all the playlists, searching for a playlist called "Zen Audio Player", if found get its id and store it
+ */
+function checkIfUserAlreadyHasZapYoutubePlaylist() {
+ var request = gapi.client.youtube.playlists.list({
+ part: "snippet",
+ mine: true,
+ maxResults: 50
+ });
+ request.execute(function (response) {
+ var isZenPlaylistFound = false;
+ for (var i = 0; i < response.items.length; i++) {
+ if (response.items[i].snippet.title === YOUTUBE_PLAYLIST_NAME) {
+ localStorage.setItem(LOCALSTORAGE_PLAYLIST_ID_KEY, response.items[i].id);
+ isZenPlaylistFound = true;
+ break;
+ }
+ }
+ getUserVideosFromZapPlaylist();
+ if (!isZenPlaylistFound) {
+ addNewPlaylistToYoutube();
+ }
+ });
+}
+/**
+ * This function adds new playlist in youtube account of the current user
+ */
+function addNewPlaylistToYoutube() {
+ // Request to insert playlist
+ var request = gapi.client.youtube.playlists.insert({
+ part: "snippet,status",
+ resource: {
+ snippet: {
+ title: YOUTUBE_PLAYLIST_NAME,
+ description: "A private playlist created with the YouTube API via Zen Audio Player"
+ },
+ status: {
+ privacyStatus: "private"
+ }
+ }
+ });
+ request.execute(function (response) {
+ var result = response.result;
+ if (result) { // get and store playlist ID
+ var playlistId = result.id;
+ localStorage.setItem(LOCALSTORAGE_PLAYLIST_ID_KEY, playlistId);
+ getUserVideosFromZapPlaylist();
+ }
+ else {
+ alert("Could not create Zen Audio Player playlist");
+ }
+ });
+}
+
+/**
+ * This function adds video to YouTube playlist
+ * @param {string} videoID videoID to be added
+ */
+function addVideoToPlaylist(videoID) {
+ var details = {
+ videoId: videoID,
+ kind: "youtube#video"
+ };
+ var request = gapi.client.youtube.playlistItems.insert({
+ part: "snippet",
+ resource: {
+ snippet: {
+ playlistId: localStorage.getItem(LOCALSTORAGE_PLAYLIST_ID_KEY),
+ resourceId: details
+ }
+ }
+ });
+ request.execute();
+}
+/**
+ * This function removes video from ZAP YouTube playlist
+ * @param {string} videoID videoID to be removed
+ */
+function removeYoutubeVideoFromPlaylist(videoID) {
+ var request = gapi.client.youtube.playlistItems.delete({
+ id: videoID
+ });
+ request.execute();
+}
+/**
+ * This function shows th login button from the page
+ * @param {boolean} hide if true the button will be hidden
+ */
+function changeSignInButtonVisibility(hide) {
+ $(document).ready(function () {
+ if (hide) {
+ $("#youtube-login").hide();
+ }
+ else {
+ $("#youtube-login").show();
+ }
+ });
+}
/*eslint-disable */
// Google Analytics goodness
-(function(i,s,o,g,r,a,m){i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){
-(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
-m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
-})(window,document,"script","//www.google-analytics.com/analytics.js","ga");
+(function (i, s, o, g, r, a, m) {
+ i["GoogleAnalyticsObject"] = r;
+ i[r] = i[r] || function () {
+ (i[r].q = i[r].q || []).push(arguments)
+ }, i[r].l = 1 * new Date();
+ a = s.createElement(o),
+ m = s.getElementsByTagName(o)[0];
+ a.async = 1;
+ a.src = g;
+ m.parentNode.insertBefore(a, m)
+})(window, document, "script", "//www.google-analytics.com/analytics.js", "ga");
ga("create", "UA-62983413-1", "auto");
ga("send", "pageview");
/*eslint-enable */
+
diff --git a/test/index.js b/test/index.js
index 8dce557a..944bf8fc 100644
--- a/test/index.js
+++ b/test/index.js
@@ -162,4 +162,4 @@ describe("Form", function () {
done();
});
});
-});
+});
\ No newline at end of file