From 448f622c6202c286003e49b56f22fa4fcf23f05e Mon Sep 17 00:00:00 2001 From: Ibrahim Ali Radwan Date: Tue, 11 Oct 2016 08:37:07 +0200 Subject: [PATCH 01/16] Add Zen Audio Player YouTube private playlist under the user authenticated account --- css/styles.css | 263 +++++----- index.html | 242 +++++---- js/everything.js | 1239 +++++++++++++++++++++++++--------------------- 3 files changed, 946 insertions(+), 798 deletions(-) diff --git a/css/styles.css b/css/styles.css index 4ac7e0ef..0f88a2e1 100644 --- a/css/styles.css +++ b/css/styles.css @@ -1,256 +1,269 @@ body { - background-color: #f9f9f9; + background-color: #f9f9f9; } -header, footer { - text-align: center; +header, +footer { + text-align: center; } header { - width: 80%; - margin: 50px auto 0 auto; + width: 80%; + margin: 50px auto 0 auto; } @media all and (min-width: 480px) { - - header { - max-width: 450px; - } - + header { + max-width: 450px; + } } -.tt-menu, footer { - background-color: #ffffff; +.tt-menu, +footer { + background-color: #ffffff; } footer { - border-top: 1px solid #eeeeee; - margin-top: 50px; - padding: 50px 0; + border-top: 1px solid #eeeeee; + margin-top: 50px; + padding: 50px 0; } hgroup { - margin-top: 30px; + margin-top: 30px; } h2 { - font-weight: normal; - font-size: 16px; - line-height: 150%; + font-weight: normal; + font-size: 16px; + line-height: 150%; } a { - color: #3498db; + color: #3498db; } .hide { - display: none; + display: none; } #audioplayer { - text-align: center; - display: none; + text-align: center; + display: none; } #container { - width: 80%; - margin: 10px auto 0; + width: 80%; + margin: 10px auto 0; } @media all and (min-width: 480px) { - - #container { - max-width: 450px; - } - + #container { + max-width: 450px; + } } #v { - margin: 0 auto; - width: 100%; - text-align: center; - font-size: 16px; - padding-left: 50px; + margin: 0 auto; + width: 100%; + text-align: center; + font-size: 16px; + padding-left: 50px; } #v::-ms-clear { - display: none; + display: none; } #player { - text-align: center; - min-width: 100%; - width: 100%; + text-align: center; + min-width: 100%; + width: 100%; } #playerControls { - margin: 8px 0 15px; + margin: 8px 0 15px; } -#zen-video-title, #demo { - margin-top: 5px; - font-weight: bold; - word-wrap: break-word; +#zen-video-title, +#demo { + margin-top: 5px; + font-weight: bold; + word-wrap: break-word; } -#zen-video-error, #zen-error { - text-align: center; +#zen-video-error, +#zen-error { + text-align: center; } -#demo { - display: block; - margin: 0 auto; +#demo:not(.expanded), +#youtube-login.visible { + display: inline-block; + width: 49.6%; + margin: 0; +} + +#demo.expanded { + display: block; + margin: 0 auto; } #zen-video-description { - white-space: pre-line; + white-space: pre-line; } #form { - margin-bottom: 20px; + margin-bottom: 20px; } + /* Media controls */ + #pause { - display: none; + display: none; } .slider-handle { - background-color: #000000; - background-image: none; + background-color: #000000; + background-image: none; } #volumeSliderControl .slider-handle.custom::before { - /* Unicode bar */ - content: "\258C"; - color: #000000; + /* Unicode bar */ + content: "\258C"; + color: #000000; } #timeSeekSliderControl { - width: 350px; + width: 350px; } #search-results { - text-align: center; - display: none; + text-align: center; + display: none; } #search-results li { - list-style-type: none; + list-style-type: none; } + /* Input field fixes and search suggestion style. * typeahead conflicts with primercss */ + .twitter-typeahead { - width: 100%; + width: 100%; } .twitter-typeahead input { - border-top-right-radius: 0; - border-bottom-right-radius: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; } .tt-menu { - border: 1px solid #cccccc; - width: 100%; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.07); + border: 1px solid #cccccc; + width: 100%; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.07); } .tt-suggestion { - text-align: center; + text-align: center; } -.tt-cursor, .tt-suggestion:hover { - background-color: #efefef; +.tt-cursor, +.tt-suggestion:hover { + background-color: #efefef; } .zen-logo { - width: 100px; - height: 100px; - display: block; - overflow: hidden; - border-radius: 6px; - margin: 0 auto; + width: 100px; + height: 100px; + display: block; + overflow: hidden; + border-radius: 6px; + margin: 0 auto; } .btn-search { - width: 50px; + width: 50px; } @media all and (min-width: 480px) { - - .input-search { - height: 50px; - } - - .btn-search { - height: 50px; - } - - .fa-search { - font-size: 20px; - } - + .input-search { + height: 50px; + } + .btn-search { + height: 50px; + } + .fa-search { + font-size: 20px; + } } .sponsor { - width: 70%; - margin: 30px 30px 0 auto; + width: 70%; + margin: 30px 30px 0 auto; } .sponsor-a { - width: 100%; - display: block; - margin-top: 20px; + width: 100%; + display: block; + margin-top: 20px; } .sponsor-a:first-of-type { - margin-top: 0; + margin-top: 0; } -@media all and (min-width: 640px) { - - .sponsor-a { - width: 30.666666667%; - float: left; - margin-left: 4%; - margin-top: 0; - } - - .sponsor-a:first-of-type { - margin: 0; - } +@media all and (max-width: 640px) { + #demo, + #youtube-login.visible { + width: calc(98.8%/2); + } +} +@media all and (min-width: 640px) { + .sponsor-a { + width: 30.666666667%; + float: left; + margin-left: 4%; + margin-top: 0; + } + .sponsor-a:first-of-type { + margin: 0; + } } .color-grey { - color: #666666; + color: #666666; } .clear-both { - clear: both; - height: 0; - overflow: hidden; + clear: both; + height: 0; + overflow: hidden; } .img-100 { - width: 100%; + width: 100%; } .plyr__video-wrapper { - display: none; + display: none; } #audioplayer .plyr__controls { - padding: 10px; - border-radius: inherit; - border: 1px solid #dbe3e8; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); - color: #565d64; - position: relative; - background: #ffffff; -} - -#audioplayer .plyr__volume--display, #audioplayer .plyr__progress--buffer { - background-color: #f9f9f9; + padding: 10px; + border-radius: inherit; + border: 1px solid #dbe3e8; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); + color: #565d64; + position: relative; + background: #ffffff; +} + +#audioplayer .plyr__volume--display, +#audioplayer .plyr__progress--buffer { + background-color: #f9f9f9; } diff --git a/index.html b/index.html index 94beb75b..7e51fc99 100644 --- a/index.html +++ b/index.html @@ -1,130 +1,156 @@ + - - - - - - - Zen Audio Player - - - - - - - - - - - - - - - - - - - + + + + + + + Zen Audio Player + + + + + + + + + + + + + + + + + + + + + -
- -
- -
- -
-

Zen Audio Player

-

Listen to YouTube videos, without the distracting visuals.

-

-
-
-
- -
-
- - +
+ +
+ +
+ +
+

Zen Audio Player

+

Listen to YouTube videos, without the distracting visuals.

+

+
+
+
+ + +
+ + -
- -
-

Search Results

-
    -
    -
    - -
    -

    +

    + +
    +

    Search Results

    +
      +
      +
      + +
      +

      -
      - - -
      - - - -
      - -
      -
      -
      -
      -
      -
      + + + +
      + +
      +
      +
      +
      +
      + -
      - - - - + + + + + + + diff --git a/js/everything.js b/js/everything.js index f97a4e02..dff24cf5 100644 --- a/js/everything.js +++ b/js/everything.js @@ -7,48 +7,48 @@ var client; * @returns {string} */ function anonymizeFileUrl() { - return isFileProtocol() ? "localhost" : window.location.href; + return isFileProtocol() ? "localhost" : window.location.href; } function sendKeenEvent(_msg, _data) { - if (!client) { - return; - } - var d = { - page_url: anonymizeFileUrl(), // eslint-disable-line camelcase - user_agent: "${keen.user_agent}", // eslint-disable-line camelcase - ip_address: "${keen.ip}", // eslint-disable-line camelcase - keen: { - addons: [ - { - name: "keen:ip_to_geo", - input: { - ip: "ip_address" - }, - output: "ip_geo_info" + if (!client) { + return; + } + var d = { + page_url: anonymizeFileUrl(), // eslint-disable-line camelcase + user_agent: "${keen.user_agent}", // eslint-disable-line camelcase + ip_address: "${keen.ip}", // eslint-disable-line camelcase + keen: { + addons: [ + { + name: "keen:ip_to_geo", + input: { + ip: "ip_address" + }, + output: "ip_geo_info" }, - { - name: "keen:ua_parser", - input: { - ua_string: "user_agent" // eslint-disable-line camelcase - }, - output: "parsed_user_agent" + { + name: "keen:ua_parser", + input: { + ua_string: "user_agent" // eslint-disable-line camelcase + }, + output: "parsed_user_agent" }, - { - name: "keen:url_parser", - input: { - url: "page_url" - }, - output: "parsed_page_url" + { + name: "keen:url_parser", + input: { + url: "page_url" + }, + output: "parsed_page_url" } ] - } - }; + } + }; - for (var _d in _data) { - d[_d] = _data[_d]; - } - client.addEvent(_msg, d); + for (var _d in _data) { + d[_d] = _data[_d]; + } + client.addEvent(_msg, d); } /** @@ -59,27 +59,29 @@ var youTubeDataApiKey = "AIzaSyCxVxsC5k46b8I-CLXlF3cZHjpiqP_myVk"; var currentVideoID; var errorMessage = { - init: function() { - // nothing for now - }, - show: function(message) { - $("#zen-error").text("ERROR: " + message); - $("#zen-error").show(); - - // Pause if we got an error - ZenPlayer.pause(); - - // When the error message is shown, also hide the player - ZenPlayer.hide(); - - // Send the error to Google Analytics - ga("send", "event", "error", message); - sendKeenEvent("error", {"message": message}); - }, - hide: function() { - $("#zen-error").text("").hide(); - ZenPlayer.show(); - } + init: function () { + // nothing for now + }, + show: function (message) { + $("#zen-error").text("ERROR: " + message); + $("#zen-error").show(); + + // Pause if we got an error + ZenPlayer.pause(); + + // When the error message is shown, also hide the player + ZenPlayer.hide(); + + // Send the error to Google Analytics + ga("send", "event", "error", message); + sendKeenEvent("error", { + "message": message + }); + }, + hide: function () { + $("#zen-error").text("").hide(); + ZenPlayer.show(); + } }; /** @@ -87,297 +89,297 @@ var errorMessage = { * @returns {boolean} */ function isFileProtocol() { - return URI(window.location).protocol() === "file"; + return URI(window.location).protocol() === "file"; } function handleYouTubeError(details) { - if (typeof details.code === "number") { - var message = "Got an unknown error, check the JS console."; - var verboseMessage = message; - - // Handle the different error codes - switch (details.code) { - case 2: - verboseMessage = "The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks."; - message = "looks like an invalid video ID"; - break; - case 5: - verboseMessage = "The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred."; - message = "we can't play that video here, or something is wrong with YouTube's iframe API"; - break; - case 100: - verboseMessage = "The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private."; - message = "we can't find that video, it might be private or removed"; - break; - case 101: - verboseMessage = "The owner of the requested video does not allow it to be played in embedded players."; - message = "the video owner won't allow us to play that video"; - break; - case 150: - verboseMessage = "This error is the same as 101. It's just a 101 error in disguise!"; - message = "the video owner won't allow us to play that video"; - break; - } - - // 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}); - - // Log debug info - console.log("Verbose debug error message: ", verboseMessage); - } + if (typeof details.code === "number") { + var message = "Got an unknown error, check the JS console."; + var verboseMessage = message; + + // Handle the different error codes + switch (details.code) { + case 2: + verboseMessage = "The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks."; + message = "looks like an invalid video ID"; + break; + case 5: + verboseMessage = "The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred."; + message = "we can't play that video here, or something is wrong with YouTube's iframe API"; + break; + case 100: + verboseMessage = "The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private."; + message = "we can't find that video, it might be private or removed"; + break; + case 101: + verboseMessage = "The owner of the requested video does not allow it to be played in embedded players."; + message = "the video owner won't allow us to play that video"; + break; + case 150: + verboseMessage = "This error is the same as 101. It's just a 101 error in disguise!"; + message = "the video owner won't allow us to play that video"; + break; + } + + // 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 + }); + + // Log debug info + console.log("Verbose debug error message: ", verboseMessage); + } } // One day, try to move all globals under the ZenPlayer object var ZenPlayer = { - updated: false, - isPlaying: false, - 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"], - hideControls: false - }); - - // Load video into Plyr player - if (plyrPlayer.plyr) { - var that = this; - plyrPlayer.addEventListener("error", function(event) { - if (event && event.detail && typeof event.detail.code === "number") { - handleYouTubeError(event.detail); - ZenPlayer.hide(); - } - }); - - 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; - that.videoDuration = plyrPlayer.plyr.embed.getDuration(); - that.videoDescription = that.getVideoDescription(videoID); - that.videoUrl = plyrPlayer.plyr.embed.getVideoUrl(); - - // Updates the time position by a given argument in URL - // IE https://zenplayer.audio/?v=koJv-j1usoI&t=30 starts at 0:30 - var t = getCurrentTimePosition(); - if (t) { - that.videoPosition = t; - window.sessionStorage[videoID] = t; - } - - // Initialize UI - that.setupTitle(); - that.setupVideoDescription(videoID); - that.setupPlyrToggle(); - }); - - plyrPlayer.addEventListener("playing", function() { - if (that.updated) { - return; - } - - // Start video from where we left off, if it makes sense - if (window.sessionStorage && window.sessionStorage.hasOwnProperty(videoID)) { - var resumeTime = window.sessionStorage[videoID]; - var videoDuration = plyrPlayer.plyr.embed.getDuration(); - if (!isNaN(resumeTime) && resumeTime < videoDuration - 3) { - plyrPlayer.plyr.embed.seekTo(resumeTime); - } - } - - that.updated = true; - - // Analytics - ga("send", "event", "Playing YouTube video title", that.videoTitle); - ga("send", "event", "Playing YouTube video author", that.videoAuthor); - ga("send", "event", "Playing YouTube video duration (seconds)", that.videoDuration); - // For some reason author is always an empty string, but not when inspected in the browser... - sendKeenEvent("Playing YouTube video", { - author: plyrPlayer.plyr.embed.getVideoData().author, - title: plyrPlayer.plyr.embed.getVideoData().title, - seconds: plyrPlayer.plyr.embed.getDuration(), - youtubeID: plyrPlayer.plyr.embed.getVideoData().video_id - }); - - // Show player - that.show(); - updateTweetMessage(); - }); - - plyrPlayer.addEventListener("timeupdate", function() { - // Store the current time of the video. - var resumeTime = 0; - if (window.sessionStorage) { - var currentTime = plyrPlayer.plyr.embed.getCurrentTime(); - var videoDuration = plyrPlayer.plyr.embed.getDuration(); - - // Only store the current time if the video isn't done - // playing yet. If the video finished already, then it - // should start off at the beginning next time. - // There is a fuzzy 3 seconds because sometimes the video - // will end a few seconds before the video duration. - if (currentTime < videoDuration - 3) { - resumeTime = currentTime; - } - window.sessionStorage[videoID] = resumeTime; - } - var updatedUrl = that.videoUrl; - if (resumeTime > 0) { - updatedUrl = that.videoUrl + "&t=" + Math.round(resumeTime); - $("#zen-video-title").attr("href", updatedUrl); - } - else if (resumeTime <= 0 && $("#zen-video-title").attr("href") !== that.videoUrl) { - updatedUrl = that.videoUrl; - } - $("#zen-video-title").attr("href", updatedUrl); - }); - - plyrPlayer.addEventListener("playing", function() { - this.isPlaying = true; - }.bind(this)); - - plyrPlayer.addEventListener("pause", function() { - this.isPlaying = false; - }.bind(this)); - - plyrPlayer.plyr.source({ - type: "video", - title: "Title", - sources: [{ - src: currentVideoID, - type: "youtube" + updated: false, + isPlaying: false, + 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"], + hideControls: false + }); + + // Load video into Plyr player + if (plyrPlayer.plyr) { + var that = this; + plyrPlayer.addEventListener("error", function (event) { + if (event && event.detail && typeof event.detail.code === "number") { + handleYouTubeError(event.detail); + ZenPlayer.hide(); + } + }); + + 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; + that.videoDuration = plyrPlayer.plyr.embed.getDuration(); + that.videoDescription = that.getVideoDescription(videoID); + that.videoUrl = plyrPlayer.plyr.embed.getVideoUrl(); + + // Updates the time position by a given argument in URL + // IE https://zenplayer.audio/?v=koJv-j1usoI&t=30 starts at 0:30 + var t = getCurrentTimePosition(); + if (t) { + that.videoPosition = t; + window.sessionStorage[videoID] = t; + } + + // Initialize UI + that.setupTitle(); + that.setupVideoDescription(videoID); + that.setupPlyrToggle(); + }); + + plyrPlayer.addEventListener("playing", function () { + if (that.updated) { + return; + } + + // Start video from where we left off, if it makes sense + if (window.sessionStorage && window.sessionStorage.hasOwnProperty(videoID)) { + var resumeTime = window.sessionStorage[videoID]; + var videoDuration = plyrPlayer.plyr.embed.getDuration(); + if (!isNaN(resumeTime) && resumeTime < videoDuration - 3) { + plyrPlayer.plyr.embed.seekTo(resumeTime); + } + } + + that.updated = true; + + // Analytics + ga("send", "event", "Playing YouTube video title", that.videoTitle); + ga("send", "event", "Playing YouTube video author", that.videoAuthor); + ga("send", "event", "Playing YouTube video duration (seconds)", that.videoDuration); + // For some reason author is always an empty string, but not when inspected in the browser... + sendKeenEvent("Playing YouTube video", { + author: plyrPlayer.plyr.embed.getVideoData().author, + title: plyrPlayer.plyr.embed.getVideoData().title, + seconds: plyrPlayer.plyr.embed.getDuration(), + youtubeID: plyrPlayer.plyr.embed.getVideoData().video_id + }); + + // Show player + that.show(); + updateTweetMessage(); + }); + + plyrPlayer.addEventListener("timeupdate", function () { + // Store the current time of the video. + var resumeTime = 0; + if (window.sessionStorage) { + var currentTime = plyrPlayer.plyr.embed.getCurrentTime(); + var videoDuration = plyrPlayer.plyr.embed.getDuration(); + + // Only store the current time if the video isn't done + // playing yet. If the video finished already, then it + // should start off at the beginning next time. + // There is a fuzzy 3 seconds because sometimes the video + // will end a few seconds before the video duration. + if (currentTime < videoDuration - 3) { + resumeTime = currentTime; + } + window.sessionStorage[videoID] = resumeTime; + } + var updatedUrl = that.videoUrl; + if (resumeTime > 0) { + updatedUrl = that.videoUrl + "&t=" + Math.round(resumeTime); + $("#zen-video-title").attr("href", updatedUrl); + } else if (resumeTime <= 0 && $("#zen-video-title").attr("href") !== that.videoUrl) { + updatedUrl = that.videoUrl; + } + $("#zen-video-title").attr("href", updatedUrl); + }); + + plyrPlayer.addEventListener("playing", function () { + this.isPlaying = true; + }.bind(this)); + + plyrPlayer.addEventListener("pause", function () { + this.isPlaying = false; + }.bind(this)); + + plyrPlayer.plyr.source({ + type: "video", + title: "Title", + sources: [{ + src: currentVideoID, + type: "youtube" }] - }); - } - }, - show: function() { - $("#audioplayer").show(); - }, - hide: function() { - $("#audioplayer").hide(); - }, - 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)) { - tmpVideoTitle = " " + tmpVideoTitle; - } - $("#zen-video-title").html(tmpVideoTitle); - $("#zen-video-title").attr("href", this.videoUrl); - }, - 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) { - toggleElement(event, "#zen-video-description", "Description"); - }); - }, - setupPlyrToggle: function() { - // Show player button click event - $("#togglePlayer").click(function(event) { - toggleElement(event, ".plyr__video-wrapper", "Player"); - }); - }, - getVideoDescription: function(videoID) { - var description = ""; - - if (isFileProtocol()) { - console.log("Skipping video description request as we're running the site locally."); - $("#toggleDescription").hide(); - } - else { - getYouTubeVideoDescription( - videoID, - youTubeDataApiKey, - function(data) { - if (data.items.length === 0) { - errorMessage.show("Video description not found"); - } - else { - description = data.items[0].snippet.description; - } - }, - function(jqXHR, textStatus, errorThrown) { - logError(jqXHR, textStatus, errorThrown, "Video Description error"); - } - ); - } - - // If there's no description to show, don't pretend there is - if (description.trim().length === 0) { - $("#toggleDescription").hide(); - } - - return description; - }, - play: function() { - plyrPlayer.plyr.embed.playVideo(); - }, - pause: function() { - plyrPlayer.plyr.embed.pauseVideo(); - } + }); + } + }, + show: function () { + $("#audioplayer").show(); + }, + hide: function () { + $("#audioplayer").hide(); + }, + 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)) { + tmpVideoTitle = " " + tmpVideoTitle; + } + $("#zen-video-title").html(tmpVideoTitle); + $("#zen-video-title").attr("href", this.videoUrl); + }, + 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) { + toggleElement(event, "#zen-video-description", "Description"); + }); + }, + setupPlyrToggle: function () { + // Show player button click event + $("#togglePlayer").click(function (event) { + toggleElement(event, ".plyr__video-wrapper", "Player"); + }); + }, + getVideoDescription: function (videoID) { + var description = ""; + + if (isFileProtocol()) { + console.log("Skipping video description request as we're running the site locally."); + $("#toggleDescription").hide(); + } else { + getYouTubeVideoDescription( + videoID, + youTubeDataApiKey, + function (data) { + if (data.items.length === 0) { + errorMessage.show("Video description not found"); + } else { + description = data.items[0].snippet.description; + } + }, + function (jqXHR, textStatus, errorThrown) { + logError(jqXHR, textStatus, errorThrown, "Video Description error"); + } + ); + } + + // If there's no description to show, don't pretend there is + if (description.trim().length === 0) { + $("#toggleDescription").hide(); + } + + return description; + }, + play: function () { + plyrPlayer.plyr.embed.playVideo(); + }, + pause: function () { + plyrPlayer.plyr.embed.pauseVideo(); + } }; /** * Create a twitter message with current song if we have one. */ function updateTweetMessage() { - var url = URI("https://ZenPlayer.Audio"); - - var opts = { - text: "Listen to YouTube videos without the distracting visuals", - hashTags: "ZenAudioPlayer", - url: url.toString() - }; - - var id = getCurrentVideoID(); - if (id) { - url.setSearch("v", id); - opts.url = url.toString(); - opts.text = "I'm listening to " + plyrPlayer.plyr.embed.getVideoData().title; - } - - twttr.widgets.createHashtagButton( - "ZenAudioPlayer", - document.getElementById("tweetButton"), - opts - ); + var url = URI("https://ZenPlayer.Audio"); + + var opts = { + text: "Listen to YouTube videos without the distracting visuals", + hashTags: "ZenAudioPlayer", + url: url.toString() + }; + + var id = getCurrentVideoID(); + if (id) { + url.setSearch("v", id); + opts.url = url.toString(); + opts.text = "I'm listening to " + plyrPlayer.plyr.embed.getVideoData().title; + } + + twttr.widgets.createHashtagButton( + "ZenAudioPlayer", + document.getElementById("tweetButton"), + opts + ); } function logError(jqXHR, textStatus, errorThrown, _errorMessage) { - var responseText = JSON.parse(jqXHR.error().responseText); - errorMessage.show(responseText.error.errors[0].message); - console.log(_errorMessage, errorThrown); + var responseText = JSON.parse(jqXHR.error().responseText); + errorMessage.show(responseText.error.errors[0].message); + console.log(_errorMessage, errorThrown); } function toggleElement(event, toggleID, buttonText) { - event.preventDefault(); + event.preventDefault(); - var toggleElement = $(toggleID); - toggleElement.toggle(); + var toggleElement = $(toggleID); + toggleElement.toggle(); - var toggleTextElement = $("#" + event.currentTarget.id); + var toggleTextElement = $("#" + event.currentTarget.id); - if (toggleElement.is(":visible")) { - toggleTextElement.text("Hide " + buttonText); - } - else { - toggleTextElement.text("Show " + buttonText); - } + if (toggleElement.is(":visible")) { + toggleTextElement.text("Hide " + buttonText); + } else { + toggleTextElement.text("Show " + buttonText); + } } /** @@ -386,17 +388,16 @@ function toggleElement(event, toggleID, buttonText) { * @return {string|null} */ function getCurrentVideoID() { - var v = URI(window.location).search(true).v; - - // If the URL has multiple v parameters, take parsing the last one (usually when ?v=someurl&v=xyz) - var r; - if (Array.isArray(v)) { - r = wrapParseYouTubeVideoID(v.pop()); - } - else if (v) { - r = wrapParseYouTubeVideoID(v); - } - return r; + var v = URI(window.location).search(true).v; + + // If the URL has multiple v parameters, take parsing the last one (usually when ?v=someurl&v=xyz) + var r; + if (Array.isArray(v)) { + r = wrapParseYouTubeVideoID(v.pop()); + } else if (v) { + r = wrapParseYouTubeVideoID(v); + } + return r; } /** @@ -404,11 +405,11 @@ function getCurrentVideoID() { * @returns {Number} */ function getCurrentTimePosition() { - var t = parseInt(URI(window.location).search(true).t, 10); - if (t > 0 && t < Number.MAX_VALUE) { - return t; - } - return 0; + var t = parseInt(URI(window.location).search(true).t, 10); + if (t > 0 && t < Number.MAX_VALUE) { + return t; + } + return 0; } /** @@ -416,7 +417,7 @@ function getCurrentTimePosition() { * @returns {string|bool|null} */ function getCurrentSearchQuery() { - return URI(window.location).search(true).q; + return URI(window.location).search(true).q; } /** @@ -425,7 +426,7 @@ function getCurrentSearchQuery() { * @returns {string} The stripped URL */ function cleanURL(url) { - return URI(url).search("").fragment(""); + return URI(url).search("").fragment(""); } /** @@ -436,15 +437,15 @@ function cleanURL(url) { * @returns {string} */ function makeListenURL(videoID, videoPosition) { - var url = cleanURL(window.location); + var url = cleanURL(window.location); - url.setSearch("v", videoID); + url.setSearch("v", videoID); - if (videoPosition) { - url.setSearch("t", videoPosition); - } + if (videoPosition) { + url.setSearch("t", videoPosition); + } - return url.toString(); + return url.toString(); } /** @@ -454,71 +455,69 @@ function makeListenURL(videoID, videoPosition) { * @returns {string} */ function makeSearchURL(searchQuery) { - return cleanURL(window.location).setSearch("q", searchQuery).toString(); + return cleanURL(window.location).setSearch("q", searchQuery).toString(); } 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 (/) */ - 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"); + /* 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 (/) */ + 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"); } function anchorTimestamps(text, videoID) { - /* RegEx to match - hh:mm:ss - h:mm:ss - mm:ss - m:ss - and wraps the timestamps in tags - RegEx explanation: - ((?:[0-5]\d|\d|) either the string is "colon 00-59" or "0-9" or "blank" - (?:\d|\:[0-5]\d) either the string is "colon 0-9" or "colon 00-59" - (?:$|\:[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 "" + match + ""; - }); + /* RegEx to match + hh:mm:ss + h:mm:ss + mm:ss + m:ss + and wraps the timestamps in tags + RegEx explanation: + ((?:[0-5]\d|\d|) either the string is "colon 00-59" or "0-9" or "blank" + (?:\d|\:[0-5]\d) either the string is "colon 0-9" or "colon 00-59" + (?:$|\:[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 "" + match + ""; + }); } function convertTimestamp(timestamp) { - var seconds = 0; - var minutes = 0; - var hours = 0; - var timeComponents = timestamp.split(":"); - if (timeComponents.length === 3) { - hours = parseInt(timeComponents[0], 10) * 60 * 60; // convert hours to seocnds - minutes = parseInt(timeComponents[1], 10) * 60; // convert minutes to seconds - seconds = parseInt(timeComponents[2], 10); // add remaining seconds - } - else { - minutes = parseInt(timeComponents[0], 10) * 60; // convert minutes to seconds - seconds = parseInt(timeComponents[1], 10); // add remaining seconds - } - return hours + minutes + seconds; + var seconds = 0; + var minutes = 0; + var hours = 0; + var timeComponents = timestamp.split(":"); + if (timeComponents.length === 3) { + hours = parseInt(timeComponents[0], 10) * 60 * 60; // convert hours to seocnds + minutes = parseInt(timeComponents[1], 10) * 60; // convert minutes to seconds + seconds = parseInt(timeComponents[2], 10); // add remaining seconds + } else { + minutes = parseInt(timeComponents[0], 10) * 60; // convert minutes to seconds + seconds = parseInt(timeComponents[1], 10); // add remaining seconds + } + return hours + minutes + seconds; } function wrapParseYouTubeVideoID(url) { - if (currentVideoID && url === currentVideoID) { - // We have already determined the video id - return currentVideoID; - } - - var info = parseYoutubeVideoID(url); - - if (info.id) { - currentVideoID = info.id; - ga("send", "event", "video ID format", info.format); - return info.id; - } - else { - errorMessage.show("Failed to parse the video ID."); - } + if (currentVideoID && url === currentVideoID) { + // We have already determined the video id + return currentVideoID; + } + + var info = parseYoutubeVideoID(url); + + if (info.id) { + currentVideoID = info.id; + ga("send", "event", "video ID format", info.format); + return info.id; + } else { + errorMessage.show("Failed to parse the video ID."); + } } // Some demo video's audio, feel free to add more @@ -526,180 +525,290 @@ 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)]; + return demos[Math.floor(Math.random() * demos.length)]; } -$(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."); - $("#mobile-message").show(); - return; - } - // Keen.io - if (typeof Keen !== "undefined") { // eslint-disable-line no-undef - client = new Keen({ // eslint-disable-line no-undef - projectId: "5690c384c1e0ab0c8a6c59c4", - writeKey: "630fa16847ce5ffb01c9cc00327498e4e7716e0f324fb14fdf0e83ffc06f9eacff5fad1313c2701efe4a91c88c34b8d8153cbb121c454056bb63caf60a46336dd9c9e9855ecc5202ef3151d798eda40896d5111f44005c707cbfb32c7ae31070d129d6f520d5604fdbce5ad31e9c7232" - }); - } - - errorMessage.init(); - - // How do we know if the value is truly invalid? - // Preload the form from the URL - var currentVideoID = getCurrentVideoID(); - if (currentVideoID) { - $("#v").attr("value", currentVideoID); - } - else { - var currentSearchQuery = getCurrentSearchQuery(); - if (currentSearchQuery) { - $("#v").attr("value", currentSearchQuery); - getSearchResults( - currentSearchQuery, - youTubeDataApiKey, - function(data) { - if (data.pageInfo.totalResults === 0) { - errorMessage.show("No results."); - return; - } - $("#search-results").show(); - // Clear out results - $("#search-results ul").html(""); - - var start = "
    • " + result.snippet.title + end); - }); - }, - function(jqXHR, textStatus, errorThrown) { - logError(jqXHR, textStatus, errorThrown, "Search error"); - } - ); - } - } - - // Autocomplete with youtube suggested queries - $("#v").typeahead({ - hint: false, - highlight: true, - minLength: 1 - }, { - source: function (query, processSync, processAsync) { - getAutocompleteSuggestions(query, function(data) { - return processAsync($.map(data[1], function(item) { - return item[0]; - })); - }); - } - }).bind("typeahead:selected", function(obj, datum) { - window.location.href = makeSearchURL(datum); - }); - - // Handle form submission - $("#form").submit(function(event) { - event.preventDefault(); - var formValue = $.trim($("#v").val()); - var formValueTime = /&t=(\d*)$/g.exec(formValue); - if (formValueTime && formValueTime.length > 1) { - formValueTime = parseInt(formValueTime[1], 10); - formValue = formValue.replace(/&t=\d*$/g, ""); - } - if (formValue) { - var videoID = wrapParseYouTubeVideoID(formValue, true); - ga("send", "event", "form submitted", videoID); - sendKeenEvent("Form submitted", {videoID: videoID}); - if (isFileProtocol()) { - errorMessage.show("Skipping video lookup request as we're running the site locally."); - } - else { - $.ajax({ - url: "https://www.googleapis.com/youtube/v3/videos", - dataType: "json", - async: false, - data: { - key: youTubeDataApiKey, - part: "snippet", - fields: "items/snippet/description", - id: videoID - }, - success: function(data) { - if (data.items.length === 0) { - window.location.href = makeSearchURL(formValue); - } - else { - window.location.href = makeListenURL(videoID, formValueTime); - } - } - }).fail(function(jqXHR, textStatus, errorThrown) { - logError(jqXHR, textStatus, errorThrown, "Lookup error"); - }); - } - } - else { - errorMessage.show("Try entering a YouTube video ID or URL!"); - } - }); - - // Hide the demo link if playing any of the demo video's audio - if ($.inArray(currentVideoID, demos) !== -1) { - $("#demo").hide(); - } - - // Handle demo link click - $("#demo").click(function(event) { - event.preventDefault(); - ga("send", "event", "demo", "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 - var pickedDemo = pickDemo(); - if (window.location.href.indexOf(demos) === -1) { - window.location.href = makeListenURL(pickedDemo); - } - else { - ga("send", "event", "demo", "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) { - // 32 = spacebar, toggle play/pause if not typing in the search box - if (evt.keyCode === 32 && !$("#v").is(":focus")) { - evt.preventDefault(); - if (ZenPlayer.isPlaying) { - ZenPlayer.pause(); - } - else { - ZenPlayer.play(); - } - } - }); - - $(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(); - } - }); +$(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."); + $("#mobile-message").show(); + return; + } + // Keen.io + if (typeof Keen !== "undefined") { // eslint-disable-line no-undef + client = new Keen({ // eslint-disable-line no-undef + projectId: "5690c384c1e0ab0c8a6c59c4", + writeKey: "630fa16847ce5ffb01c9cc00327498e4e7716e0f324fb14fdf0e83ffc06f9eacff5fad1313c2701efe4a91c88c34b8d8153cbb121c454056bb63caf60a46336dd9c9e9855ecc5202ef3151d798eda40896d5111f44005c707cbfb32c7ae31070d129d6f520d5604fdbce5ad31e9c7232" + }); + } + + errorMessage.init(); + + // How do we know if the value is truly invalid? + // Preload the form from the URL + var currentVideoID = getCurrentVideoID(); + if (currentVideoID) { + $("#v").attr("value", currentVideoID); + } else { + var currentSearchQuery = getCurrentSearchQuery(); + if (currentSearchQuery) { + $("#v").attr("value", currentSearchQuery); + getSearchResults( + currentSearchQuery, + youTubeDataApiKey, + function (data) { + if (data.pageInfo.totalResults === 0) { + errorMessage.show("No results."); + return; + } + $("#search-results").show(); + // Clear out results + $("#search-results ul").html(""); + + var start = "
    • " + result.snippet.title + end); + }); + }, + function (jqXHR, textStatus, errorThrown) { + logError(jqXHR, textStatus, errorThrown, "Search error"); + } + ); + } + } + + // Autocomplete with youtube suggested queries + $("#v").typeahead({ + hint: false, + highlight: true, + minLength: 1 + }, { + source: function (query, processSync, processAsync) { + getAutocompleteSuggestions(query, function (data) { + return processAsync($.map(data[1], function (item) { + return item[0]; + })); + }); + } + }).bind("typeahead:selected", function (obj, datum) { + window.location.href = makeSearchURL(datum); + }); + + // Handle form submission + $("#form").submit(function (event) { + event.preventDefault(); + var formValue = $.trim($("#v").val()); + var formValueTime = /&t=(\d*)$/g.exec(formValue); + if (formValueTime && formValueTime.length > 1) { + formValueTime = parseInt(formValueTime[1], 10); + formValue = formValue.replace(/&t=\d*$/g, ""); + } + if (formValue) { + var videoID = wrapParseYouTubeVideoID(formValue, true); + ga("send", "event", "form submitted", videoID); + sendKeenEvent("Form submitted", { + videoID: videoID + }); + if (isFileProtocol()) { + errorMessage.show("Skipping video lookup request as we're running the site locally."); + } else { + $.ajax({ + url: "https://www.googleapis.com/youtube/v3/videos", + dataType: "json", + async: false, + data: { + key: youTubeDataApiKey, + part: "snippet", + fields: "items/snippet/description", + id: videoID + }, + success: function (data) { + if (data.items.length === 0) { + window.location.href = makeSearchURL(formValue); + } else { + window.location.href = makeListenURL(videoID, formValueTime); + } + } + }).fail(function (jqXHR, textStatus, errorThrown) { + logError(jqXHR, textStatus, errorThrown, "Lookup error"); + }); + } + } else { + errorMessage.show("Try entering a YouTube video ID or URL!"); + } + }); + + // Hide the demo link if playing any of the demo video's audio + if ($.inArray(currentVideoID, demos) !== -1) { + $("#demo").hide(); + } + + // Handle demo link click + $("#demo").click(function (event) { + event.preventDefault(); + ga("send", "event", "demo", "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 + var pickedDemo = pickDemo(); + if (window.location.href.indexOf(demos) === -1) { + window.location.href = makeListenURL(pickedDemo); + } else { + ga("send", "event", "demo", "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) { + // 32 = spacebar, toggle play/pause if not typing in the search box + if (evt.keyCode === 32 && !$("#v").is(":focus")) { + evt.preventDefault(); + if (ZenPlayer.isPlaying) { + ZenPlayer.pause(); + } else { + ZenPlayer.play(); + } + } + }); + + $(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'; +var OAUTH2_SCOPES = 'https://www.googleapis.com/auth/youtube'; +var auth2; // OAuth2 variable +var tmpYouTubeApiKey = 'AIzaSyBvkfhXrqTMk8WJuhN4CeRrIg4BUm5Md0E'; + +/** + * This function will get called wheve the page loads successfully + */ +function handleClientLoad() { + if (!localStorage.getItem("zap_playlist_id")) { + // Load the API client and auth library + gapi.load('client:auth2', initAuth); + } else { + hideSignInButton(); + } +} + +/** + * This function initializes the authentication using YouTube API + */ +function initAuth() { + gapi.client.setApiKey(tmpYouTubeApiKey); + gapi.auth2.init({ + client_id: OAUTH2_CLIENT_ID, + scope: OAUTH2_SCOPES + }).then(function () { + auth2 = gapi.auth2.getAuthInstance(); + // Listen for sign-in state changes. + auth2.isSignedIn.listen(updateSigninStatus); + // Handle the initial sign-in state. + updateSigninStatus(auth2.isSignedIn.get()); + $("#youtube-login").click(handleAuthClick); + }); +} +/** + * When the app sign in status changes this function gets called + * @param {boolean} isSignedIn is the sign in succeeded + */ +function updateSigninStatus(isSignedIn) { + if (isSignedIn) { + hideSignInButton(); + makeApiCall(); + } +} +/** + * This function handles the sign in button click + * @param {object} event click event object + */ +function handleAuthClick(event) { + auth2.signIn(); +} + +/** + * 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 contains the requests that will be executed after the load completed + */ +function handleAPILoaded() { + // Request to insert playlist + var request = gapi.client.youtube.playlists.insert({ + part: 'snippet,status', + resource: { + snippet: { + title: 'Zen Audio Player', + description: 'A private playlist created with the YouTube API via Zen Audio Player' + }, + status: { + privacyStatus: 'private' + } + } + }); + request.execute(function (response) { + console.log(response); + var result = response.result; + if (result) { + playlistId = result.id; + localStorage.setItem("zap_playlist_id", playlistId); + } else { + alert('Could not create Zen Audio Player playlist') + } + }); +} + +/** + * This function hides th login button from the page + */ +function hideSignInButton() { + $(document).ready(function () { + $('#demo').addClass('expanded'); + $('#youtube-login').hide(); + $('#youtube-login').removeClass('visible'); + }); +} + /*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 */ From 49c4a99c74f0ede02838c06b8f2274a64295e251 Mon Sep 17 00:00:00 2001 From: Ibrahim Ali Radwan Date: Wed, 12 Oct 2016 15:14:22 +0200 Subject: [PATCH 02/16] Add videos to playlists if they don't exist --- css/styles.css | 4 ++ index.html | 4 +- js/everything.js | 155 +++++++++++++++++++++++++++++++---------------- 3 files changed, 109 insertions(+), 54 deletions(-) diff --git a/css/styles.css b/css/styles.css index 0f88a2e1..1fe89c9b 100644 --- a/css/styles.css +++ b/css/styles.css @@ -97,6 +97,10 @@ a { text-align: center; } +#youtube-login { + display: none; +} + #demo:not(.expanded), #youtube-login.visible { display: inline-block; diff --git a/index.html b/index.html index 7e51fc99..c59f1227 100644 --- a/index.html +++ b/index.html @@ -94,11 +94,11 @@


      - - diff --git a/js/everything.js b/js/everything.js index dff24cf5..6ca6a9fc 100644 --- a/js/everything.js +++ b/js/everything.js @@ -186,6 +186,9 @@ var ZenPlayer = { that.setupTitle(); that.setupVideoDescription(videoID); that.setupPlyrToggle(); + + // Add the video to the playlist if not already in list + }); plyrPlayer.addEventListener("playing", function () { @@ -703,54 +706,48 @@ var tmpYouTubeApiKey = 'AIzaSyBvkfhXrqTMk8WJuhN4CeRrIg4BUm5Md0E'; * This function will get called wheve the page loads successfully */ function handleClientLoad() { - if (!localStorage.getItem("zap_playlist_id")) { - // Load the API client and auth library - gapi.load('client:auth2', initAuth); - } else { - hideSignInButton(); - } + // Load the API client and auth library + gapi.load('client:auth2', checkAuth); } /** - * This function initializes the authentication using YouTube API + * This function checks the authentication using YouTube API, exactly after the page loads */ -function initAuth() { +function checkAuth() { gapi.client.setApiKey(tmpYouTubeApiKey); - gapi.auth2.init({ + gapi.auth.authorize({ client_id: OAUTH2_CLIENT_ID, - scope: OAUTH2_SCOPES - }).then(function () { - auth2 = gapi.auth2.getAuthInstance(); - // Listen for sign-in state changes. - auth2.isSignedIn.listen(updateSigninStatus); - // Handle the initial sign-in state. - updateSigninStatus(auth2.isSignedIn.get()); - $("#youtube-login").click(handleAuthClick); - }); + scope: OAUTH2_SCOPES, + immediate: true + }, handleAuthResult); } /** - * When the app sign in status changes this function gets called - * @param {boolean} isSignedIn is the sign in succeeded + * This function handles the result of the authentication call + * @param {object} authResult authentication result - Generated by library */ -function updateSigninStatus(isSignedIn) { - if (isSignedIn) { - hideSignInButton(); +function handleAuthResult(authResult) { + if (authResult && !authResult.error) { + // Authorization was successful. Hide authorization prompts and show + // content that should be visible after authorization succeeds. makeApiCall(); + } else { + showSignInButton(); + // 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 handles the sign in button click - * @param {object} event click event object - */ -function handleAuthClick(event) { - auth2.signIn(); -} /** * 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(); }); @@ -758,41 +755,95 @@ function makeApiCall() { /** * This function contains the requests that will be executed after the load completed + * If the localStorage doesn't contain zap_playlist_id: the call will be to create the playlist in the user YouTube account and then store the zap_playlist_id */ function handleAPILoaded() { - // Request to insert playlist - var request = gapi.client.youtube.playlists.insert({ - part: 'snippet,status', + // if not video is currently playing + if (!getCurrentVideoID()) { + if (!localStorage.getItem("zap_playlist_id")) { + // Request to insert playlist + var request = gapi.client.youtube.playlists.insert({ + part: 'snippet,status', + resource: { + snippet: { + title: 'Zen Audio Player', + 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 + playlistId = result.id; + localStorage.setItem("zap_playlist_id", playlistId); + } else { + alert('Could not create Zen Audio Player playlist') + } + }); + } + } else { + // Check if the video doesn't exist, insert it. + checkIfVideoExistsInPlaylistThenAddIt(currentVideoID); + } +} +/** + * This function checks if the currently playing video is already added to the user's playlist, if so ignore it, else add it + * @param {string} videoID videoID + * @returns {boolean} True if the video already exists in the playlist + */ +function checkIfVideoExistsInPlaylistThenAddIt(videoID) { + // Get the videos of the playlist + var request = gapi.client.youtube.playlistItems.list({ + part: 'snippet', + mine: true, + playlistId: localStorage.getItem("zap_playlist_id") + }); + var doesTheVideoExist = false; + request.execute(function (response) { + for (var i = 0; i < response.items.length; i++) { + if (response.items[i].snippet.resourceId.videoId === videoID) { + doesTheVideoExist = true; + break; + } + } + if(!doesTheVideoExist){ + addVideoToPlaylist(videoID); + } + }); +} +/** + * 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: { - title: 'Zen Audio Player', - description: 'A private playlist created with the YouTube API via Zen Audio Player' - }, - status: { - privacyStatus: 'private' + playlistId: localStorage.getItem("zap_playlist_id"), + resourceId: details } } }); request.execute(function (response) { console.log(response); - var result = response.result; - if (result) { - playlistId = result.id; - localStorage.setItem("zap_playlist_id", playlistId); - } else { - alert('Could not create Zen Audio Player playlist') - } }); } - /** - * This function hides th login button from the page + * This function shows th login button from the page */ -function hideSignInButton() { +function showSignInButton() { $(document).ready(function () { - $('#demo').addClass('expanded'); - $('#youtube-login').hide(); - $('#youtube-login').removeClass('visible'); + $('#demo').removeClass('expanded'); + $('#youtube-login').show(); + $('#youtube-login').addClass('visible'); }); } From 7526e02fa09378ef90b11c4c89a91b2bdb15d9e3 Mon Sep 17 00:00:00 2001 From: Ibrahim Ali Radwan Date: Wed, 12 Oct 2016 22:40:56 +0200 Subject: [PATCH 03/16] Reuse already made Zap playlist instead of creating one in case of loosing playlist ID --- js/everything.js | 99 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 30 deletions(-) diff --git a/js/everything.js b/js/everything.js index 6ca6a9fc..73b5aeec 100644 --- a/js/everything.js +++ b/js/everything.js @@ -701,6 +701,8 @@ var OAUTH2_CLIENT_ID = '763547295554-ubbn5qqth37ov4j11a2jt8mjl95eqm2l.apps.googl var OAUTH2_SCOPES = 'https://www.googleapis.com/auth/youtube'; var auth2; // OAuth2 variable var tmpYouTubeApiKey = 'AIzaSyBvkfhXrqTMk8WJuhN4CeRrIg4BUm5Md0E'; +var YOUTUBE_PLAYLIST_NAME = 'Zen Audio Player'; +var LOCALSTORAGE_PLAYLIST_ID_KEY = 'zap_playlist_id'; /** * This function will get called wheve the page loads successfully @@ -729,6 +731,8 @@ function handleAuthResult(authResult) { if (authResult && !authResult.error) { // Authorization was successful. Hide authorization prompts and show // content that should be visible after authorization succeeds. + + hideSignInButton(); makeApiCall(); } else { showSignInButton(); @@ -759,36 +763,60 @@ function makeApiCall() { */ function handleAPILoaded() { // if not video is currently playing - if (!getCurrentVideoID()) { - if (!localStorage.getItem("zap_playlist_id")) { - // Request to insert playlist - var request = gapi.client.youtube.playlists.insert({ - part: 'snippet,status', - resource: { - snippet: { - title: 'Zen Audio Player', - 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 - playlistId = result.id; - localStorage.setItem("zap_playlist_id", playlistId); - } else { - alert('Could not create Zen Audio Player playlist') - } - }); - } - } else { + if (!getCurrentVideoID() && !localStorage.getItem(LOCALSTORAGE_PLAYLIST_ID_KEY)) { + if (checkIfUserAlreadyHaveZapYoutubePlaylist() && !localStorage.getItem(LOCALSTORAGE_PLAYLIST_ID_KEY)) + addNewPlaylistIntoYoutube(); + } else if (getCurrentVideoID() && localStorage.getItem(LOCALSTORAGE_PLAYLIST_ID_KEY)) { // Check if the video doesn't exist, insert it. checkIfVideoExistsInPlaylistThenAddIt(currentVideoID); } } +/** + * 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 checkIfUserAlreadyHaveZapYoutubePlaylist() { + var request = gapi.client.youtube.playlists.list({ + part: 'snippet', + mine: true, + maxResults: 50 + }); + request.execute(function (response) { + 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); + break; + } + } + console.log(localStorage.getItem(LOCALSTORAGE_PLAYLIST_ID_KEY)); + }); +} +/** + * This function adds new playlist in youtube account of the current user + */ +function addNewPlaylistIntoYoutube() { + // 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 + playlistId = result.id; + localStorage.setItem(LOCALSTORAGE_PLAYLIST_ID_KEY, playlistId); + } else { + alert('Could not create Zen Audio Player playlist') + } + }); +} /** * This function checks if the currently playing video is already added to the user's playlist, if so ignore it, else add it * @param {string} videoID videoID @@ -799,7 +827,8 @@ function checkIfVideoExistsInPlaylistThenAddIt(videoID) { var request = gapi.client.youtube.playlistItems.list({ part: 'snippet', mine: true, - playlistId: localStorage.getItem("zap_playlist_id") + maxResults: 50, + playlistId: localStorage.getItem(LOCALSTORAGE_PLAYLIST_ID_KEY) }); var doesTheVideoExist = false; request.execute(function (response) { @@ -809,7 +838,7 @@ function checkIfVideoExistsInPlaylistThenAddIt(videoID) { break; } } - if(!doesTheVideoExist){ + if (!doesTheVideoExist) { addVideoToPlaylist(videoID); } }); @@ -827,13 +856,23 @@ function addVideoToPlaylist(videoID) { part: 'snippet', resource: { snippet: { - playlistId: localStorage.getItem("zap_playlist_id"), + playlistId: localStorage.getItem(LOCALSTORAGE_PLAYLIST_ID_KEY), resourceId: details } } }); request.execute(function (response) { - console.log(response); + //console.log(response); + }); +} +/** + * This function shows th login button from the page + */ +function hideSignInButton() { + $(document).ready(function () { + $('#demo').addClass('expanded'); + $('#youtube-login').hide(); + $('#youtube-login').removeClass('visible'); }); } /** From 9b3b3bbb0a42a2038430c9b2c92a61c0f382b244 Mon Sep 17 00:00:00 2001 From: Ibrahim Ali Radwan Date: Sat, 15 Oct 2016 15:47:36 +0200 Subject: [PATCH 04/16] Fix some styling Add soem constants to avoid hard-conding --- css/styles.css | 24 ++++++++++-------------- index.html | 6 ++++-- js/everything.js | 42 +++++++++++++++++------------------------- 3 files changed, 31 insertions(+), 41 deletions(-) diff --git a/css/styles.css b/css/styles.css index 1fe89c9b..cf973c1c 100644 --- a/css/styles.css +++ b/css/styles.css @@ -97,21 +97,24 @@ a { text-align: center; } +#demo, #youtube-login { - display: none; + display: inline-block; + width: 49%; + margin: 0 auto; } -#demo:not(.expanded), -#youtube-login.visible { - display: inline-block; - width: 49.6%; - margin: 0; +#youtube-login { + display: none; } -#demo.expanded { + +/* +#demo { display: block; margin: 0 auto; } +*/ #zen-video-description { white-space: pre-line; @@ -220,13 +223,6 @@ a { margin-top: 0; } -@media all and (max-width: 640px) { - #demo, - #youtube-login.visible { - width: calc(98.8%/2); - } -} - @media all and (min-width: 640px) { .sponsor-a { width: 30.666666667%; diff --git a/index.html b/index.html index c59f1227..ed048302 100644 --- a/index.html +++ b/index.html @@ -94,15 +94,17 @@


      - - +