[deploy] Decorator podcast episode metadata (#7303)

* Moves podcast episode metadata to decorator

* Updates the podcast episode metadata to use image_url

* Syntax tweaks, initializeMedia() fix for initialized audio & brought back use strict
This commit is contained in:
Fernando Valverde 2020-04-17 12:52:11 -06:00 committed by GitHub
parent 61b6f8ea3e
commit f2ee0378f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 137 additions and 91 deletions

View file

@ -1,4 +1,4 @@
'use strict'
"use strict"
/**
* This script hunts for podcast's "Record" for both the podcast_episde's
@ -20,7 +20,7 @@
* - saveMediaState()
*
* The following are useful eslint disables for this file in particular. Because
* of the way it's wrapped around it's own function (own context) we don't have
* of the way it's wrapped around its own function (own context) we don't have
* the problem of using a method before it's defined:
*/
@ -50,18 +50,20 @@ function initializePodcastPlayback() {
volume: 1,
duration: 1,
updated: new Date().getTime(),
windowName: window.name
windowName: window.name,
};
}
function currentAudioState() {
try {
var currentState = JSON.parse(localStorage.getItem('media_playback_state_v2'));
var currentState = JSON.parse(
localStorage.getItem('media_playback_state_v2'),
);
if (!currentState || window.name !== currentState.windowName) {
return newAudioState();
}
return currentState;
} catch(e) {
} catch (e) {
console.log(e); // eslint-disable-line no-console
return newAudioState();
}
@ -133,26 +135,26 @@ function initializePodcastPlayback() {
function applyOnclickToPodcastBar(audio) {
var currentState = currentAudioState();
getById('barPlayPause').onclick = function() {
getById('barPlayPause').onclick = function () {
playPause(audio);
};
getById('mutebutt').onclick = function() {
getById('mutebutt').onclick = function () {
muteUnmute(audio);
};
getById('volbutt').onclick = function() {
getById('volbutt').onclick = function () {
muteUnmute(audio);
};
getById('bufferwrapper').onclick = function(e) {
getById('bufferwrapper').onclick = function (e) {
goToTime(e, audio);
};
getById('volumeslider').value = currentState.volume * 100;
getById('volumeslider').onchange = function(e) {
getById('volumeslider').onchange = function (e) {
updateVolume(e, audio);
};
getById('speed').onclick = function() {
getById('speed').onclick = function () {
changePlaybackRate(audio);
};
getById('closebutt').onclick = function() {
getById('closebutt').onclick = function () {
terminatePodcastBar(audio);
};
}
@ -162,7 +164,7 @@ function initializePodcastPlayback() {
}
function updateProgressListener(audio) {
return function(e) {
return function (e) {
var bufferValue = 0;
if (audio.currentTime > 0) {
var bufferEnd = audio.buffered.end(audio.buffered.length - 1);
@ -174,7 +176,10 @@ function initializePodcastPlayback() {
function loadAudio(audio) {
if (isNativeIOS()) {
sendPodcastMessage({'action': 'load', 'url': audio.querySelector('source').src});
sendPodcastMessage({
action: 'load',
url: audio.querySelector('source').src,
});
} else {
audio.load();
}
@ -193,10 +198,10 @@ function initializePodcastPlayback() {
function findAndApplyOnclickToRecords() {
var records = findRecords();
Array.prototype.forEach.call(records, function(record) {
Array.prototype.forEach.call(records, function (record) {
var episodeSlug = record.getAttribute('data-episode');
var podcastSlug = record.getAttribute('data-podcast');
record.onclick = function() {
record.onclick = function () {
if (podcastBarAlreadyExistAndPlayingTargetEpisode(episodeSlug)) {
var audio = getById('audio');
if (audio) {
@ -214,7 +219,7 @@ function initializePodcastPlayback() {
var currentState = currentAudioState();
var el = getById('speed');
var speed = parseFloat(el.getAttribute('data-speed'));
if (speed == 2) {
if (speed === 2) {
el.setAttribute('data-speed', 0.5);
el.innerHTML = '0.5x';
currentState.playbackRate = 0.5;
@ -226,7 +231,10 @@ function initializePodcastPlayback() {
saveMediaState(currentState);
if (isNativeIOS()) {
sendPodcastMessage({'action':'rate', 'rate': currentState.playbackRate.toString()});
sendPodcastMessage({
action: 'rate',
rate: currentState.playbackRate.toString(),
});
} else {
audio.playbackRate = currentState.playbackRate;
}
@ -264,45 +272,61 @@ function initializePodcastPlayback() {
return new Promise(function (resolve, reject) {
var currentState = currentAudioState();
if (isNativeIOS()) {
sendPodcastMessage({'action': 'play', 'seconds': currentState.currentTime.toString()});
sendPodcastMessage({
action: 'play',
seconds: currentState.currentTime.toString(),
});
setPlaying(true);
resolve();
} else {
audio.currrentTime = currentState.currentTime;
audio.play().then(function() {
setPlaying(true);
resolve();
}).catch(function(error) {
console.log(error); // eslint-disable-line no-console
setPlaying(false);
reject();
});
audio
.play()
.then(function () {
setPlaying(true);
resolve();
})
.catch(function (error) {
console.log(error); // eslint-disable-line no-console
setPlaying(false);
reject();
});
}
})
});
}
function sendMetadataMessage() {
try {
var episodeContainer = getByClass('podcast-episode-container')[0];
if (episodeContainer === undefined) {
episodeContainer = getByClass('podcastliquidtag')[0];
}
var metadata = JSON.parse(episodeContainer.dataset.meta);
var message = {
action: 'metadata',
episodeName: metadata.episodeName,
podcastName: metadata.podcastName,
podcastImageUrl: metadata.podcastImageUrl,
};
sendPodcastMessage(message);
} catch (e) {
console.log('Unable to load Podcast Episode metadata', e); // eslint-disable-line no-console
}
}
function startAudioPlayback(audio) {
if (isNativeIOS()) {
try {
var episodeContainer = document.getElementsByClassName('podcast-episode-container')[0];
var metadata = JSON.parse(episodeContainer.dataset.meta);
var message = {
'action': 'metadata',
'episodeName': metadata.episodeName,
'podcastName': metadata.podcastName,
'podcastImageUrl': metadata.podcastImageUrl
}
sendPodcastMessage(message);
} catch(e) {
console.log('Unable to load Podcast Episode metadata', e); // eslint-disable-line no-console
}
sendMetadataMessage();
}
playAudio(audio).then(function() {
playAudio(audio)
.then(function () {
spinPodcastRecord();
startPodcastBar();
}).catch(function(error) {
})
.catch(function (error) {
playAudio(audio);
setTimeout(function() {
setTimeout(function () {
spinPodcastRecord('initializing...');
startPodcastBar();
}, 5);
@ -311,7 +335,7 @@ function initializePodcastPlayback() {
function pauseAudioPlayback(audio) {
if (isNativeIOS()) {
sendPodcastMessage({'action': 'pause'});
sendPodcastMessage({ action: 'pause' });
} else {
audio.pause();
}
@ -352,14 +376,25 @@ function initializePodcastPlayback() {
function muteUnmute(audio) {
var currentState = currentAudioState();
getById('mutebutt').classList.add(currentState.muted ? 'hidden' : 'showing');
getById('volumeindicator').classList.add(currentState.muted ? 'showing' : 'hidden');
getById('mutebutt').classList.remove(currentState.muted ? 'showing' : 'hidden');
getById('volumeindicator').classList.remove(currentState.muted ? 'hidden' : 'showing');
getById('mutebutt').classList.add(
currentState.muted ? 'hidden' : 'showing',
);
getById('volumeindicator').classList.add(
currentState.muted ? 'showing' : 'hidden',
);
getById('mutebutt').classList.remove(
currentState.muted ? 'showing' : 'hidden',
);
getById('volumeindicator').classList.remove(
currentState.muted ? 'hidden' : 'showing',
);
currentState.muted = !currentState.muted;
if (isNativeIOS()) {
sendPodcastMessage({'action': 'muted', 'muted': currentState.muted.toString()});
sendPodcastMessage({
action: 'muted',
muted: currentState.muted.toString(),
});
} else {
audio.muted = currentState.muted;
}
@ -370,7 +405,7 @@ function initializePodcastPlayback() {
var currentState = currentAudioState();
currentState.volume = e.target.value / 100;
if (isNativeIOS()) {
sendPodcastMessage({'action': 'volume', 'volume': currentState.volume});
sendPodcastMessage({ action: 'volume', volume: currentState.volume });
} else {
audio.volume = currentState.volume;
}
@ -385,7 +420,7 @@ function initializePodcastPlayback() {
var firstDecimal = currentTime - Math.floor(currentTime);
if (currentTime > 0) {
value = Math.floor((100.0 / duration) * currentTime);
if(firstDecimal < 0.4) {
if (firstDecimal < 0.4) {
// Rewrite to mediaState storage every few beats.
var currentState = currentAudioState();
currentState.duration = duration;
@ -397,9 +432,7 @@ function initializePodcastPlayback() {
progress.style.width = value + '%';
buffer.style.width = bufferValue + '%';
time.innerHTML =
readableDuration(currentTime) +
' / ' +
readableDuration(duration);
readableDuration(currentTime) + ' / ' + readableDuration(duration);
}
}
@ -413,9 +446,12 @@ function initializePodcastPlayback() {
currentState.currentTime = duration * percent; // jumps to 29th secs
if (isNativeIOS()) {
sendPodcastMessage({'action': 'seek', 'seconds': currentState.currentTime.toString()});
sendPodcastMessage({
action: 'seek',
seconds: currentState.currentTime.toString(),
});
} else {
audio.currentTime = currentState.currentTime
audio.currentTime = currentState.currentTime;
}
time.innerHTML =
@ -436,12 +472,16 @@ function initializePodcastPlayback() {
}
function terminatePodcastBar(audio) {
audio.removeEventListener('timeupdate', updateProgressListener(audio), false);
audio.removeEventListener(
'timeupdate',
updateProgressListener(audio),
false,
);
getById('audiocontent').innerHTML = '';
stopRotatingActivePodcastIfExist();
saveMediaState(newAudioState());
if (isNativeIOS()) {
sendPodcastMessage({'action': 'terminate'});
sendPodcastMessage({ action: 'terminate' });
}
}
@ -454,7 +494,7 @@ function initializePodcastPlayback() {
try {
var messageData = getById('audiocontent').dataset.podcast;
message = JSON.parse(messageData);
} catch(e) {
} catch (e) {
console.log(e); // eslint-disable-line no-console
return;
}
@ -466,13 +506,13 @@ function initializePodcastPlayback() {
saveMediaState(currentState);
updateProgress(currentState.currentTime, currentState.duration, 100);
} else {
console.log('Unrecognized podcast message: ', message); // eslint-disable-line no-console
console.log('Unrecognized podcast message: ', message); // eslint-disable-line no-console
}
}
function addMutationObserver() {
var mutationObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var mutationObserver = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
handlePodcastMessages(mutation);
});
});
@ -483,8 +523,8 @@ function initializePodcastPlayback() {
var currentState = currentAudioState();
document.getElementById('audiocontent').innerHTML = currentState.html;
var audio = getById('audio');
if (audio == undefined) {
audioInitialized = false
if (audio === undefined || audio === null) {
audioInitialized = false;
return;
}
if (!isNativeIOS()) {
@ -492,15 +532,18 @@ function initializePodcastPlayback() {
}
loadAudio(audio);
if (currentState.playing) {
playAudio(audio).catch(function(error) {
console.log(error); // eslint-disable-line no-console
playAudio(audio).catch(function (error) {
pausePodcastBar();
});
}
setTimeout(function(){
audio.addEventListener('timeupdate', updateProgressListener(audio), false);
setTimeout(function () {
audio.addEventListener(
'timeupdate',
updateProgressListener(audio),
false,
);
addMutationObserver();
},500);
}, 500);
applyOnclickToPodcastBar(audio);
}
@ -518,7 +561,8 @@ function initializePodcastPlayback() {
}
var audio = getById('audio');
var audioContent = getById('audiocontent');
if (audio && audioContent && audioContent.innerHTML.length < 25) { // audio not already loaded
if (audio && audioContent && audioContent.innerHTML.length < 25) {
// audio not already loaded
loadAudio(audio);
}
}

View file

@ -25,4 +25,13 @@ class PodcastEpisodeDecorator < ApplicationDecorator
published_at.utc.iso8601
end
def mobile_player_metadata
image_url = ApplicationController.helpers.cloudinary(podcast.image_url, 600)
{
podcastName: podcast.title,
episodeName: title,
podcastImageUrl: image_url
}
end
end

View file

@ -148,14 +148,6 @@ class PodcastEpisode < ApplicationRecord
[]
end
def mobile_player_metadata
{
podcastName: podcast.title,
episodeName: title,
podcastImageUrl: ApplicationController.helpers.app_url(podcast.image_url)
}
end
private
def index_id

View file

@ -2,7 +2,7 @@
type: "fetch", quality: "auto", sign_url: true,
flags: "progressive", fetch_format: "jpg") %>
<div class="podcastliquidtag" style="background: #<%= podcast.main_color_hex %> url(<%= image_url %>)">
<div class="podcastliquidtag" style="background: #<%= podcast.main_color_hex %> url(<%= image_url %>)" data-meta="<%= episode.decorate.mobile_player_metadata.to_json %>">
<div class="podcastliquidtag__info">
<a href="/<%= podcast.slug %>/<%= episode.slug %>">
<h1 class="podcastliquidtag__info__episodetitle"><%= episode.title %></h1>

View file

@ -28,7 +28,7 @@
<% end %>
<div class="podcast-episode-container" data-meta="<%= @episode.mobile_player_metadata.to_json %>">
<div class="podcast-episode-container" data-meta="<%= @episode.decorate.mobile_player_metadata.to_json %>">
<div class="hero">
<% image_url = cl_image_path(@podcast.pattern_image_url || "https://i.imgur.com/fKYKgo4.png",
type: "fetch",

View file

@ -77,4 +77,15 @@ RSpec.describe PodcastEpisodeDecorator, type: :decorator do
expect(pe.decorate.published_timestamp).to eq(published_at.utc.iso8601)
end
end
describe "#mobile_player_metadata" do
it "responds with a hash with metadata used in native mobile players" do
pe = build(:podcast_episode)
metadata = pe.decorate.mobile_player_metadata
expect(metadata).to be_instance_of(Hash)
expect(metadata[:podcastName]).to eq(pe.podcast.title)
expect(metadata[:episodeName]).to eq(pe.title)
expect(metadata[:podcastImageUrl]).to include(pe.podcast.image_url)
end
end
end

View file

@ -67,16 +67,6 @@ RSpec.describe PodcastEpisode, type: :model do
end
end
describe "#mobile_player_metadata" do
it "responds with a hash with metadata used in native mobile players" do
metadata = podcast_episode.mobile_player_metadata
expect(metadata).to be_instance_of(Hash)
expect(metadata[:podcastName]).to eq(podcast_episode.podcast.title)
expect(metadata[:episodeName]).to eq(podcast_episode.title)
expect(metadata[:podcastImageUrl]).to include(podcast_episode.podcast.image_url)
end
end
describe ".available" do
let_it_be(:podcast) { create(:podcast) }