Created
March 8, 2022 22:42
-
-
Save jmarsh24/5a53efbb41c6f41026eeab122efedf91 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Controller } from "@hotwired/stimulus"; | |
export default class extends Controller { | |
static targets = [ | |
"button", | |
"startTime", | |
"endTime", | |
"playbackSpeed", | |
"source", | |
]; | |
static values = { | |
source: String, | |
originalText: String, | |
successDurationValue: Number, | |
startTime: Number, | |
endTime: Number, | |
url: String, | |
rootUrl: String, | |
playbackSpeed: Number, | |
}; | |
initialize() { | |
this.successDurationValue = 2000; | |
} | |
connect() { | |
this.originalText = this.buttonTarget.innerHTML; | |
this.playbackSpeedValue = this.playbackSpeedTarget.value; | |
this.startTimeValue = this.parseTime(this.startTimeTarget.value); | |
this.endTimeValue = this.parseTime(this.endTimeTarget.value); | |
this.urlValueUpdate(); | |
} | |
copy() { | |
navigator.clipboard.writeText(this.urlValue); | |
this.copied(); | |
} | |
copied() { | |
this.buttonTarget.innerText = this.data.get("successContent"); | |
setTimeout(() => { | |
this.buttonTarget.innerHTML = this.originalText; | |
}, this.successDurationValue); | |
} | |
parseTime(time) { | |
var timeArray = time.toString().split(":"); | |
var timeInSeconds = 0; | |
if (timeArray.length == 2) { | |
timeInSeconds = +timeArray[0] * 60 + +timeArray[1]; | |
} | |
if (timeArray.length == 1) { | |
timeInSeconds = timeArray[0]; | |
} | |
return parseInt(timeInSeconds); | |
} | |
urlValueUpdate() { | |
this.urlValue = `${this.data.get("rootUrl")}watch?v=${this.data.get( | |
"videoId" | |
)}`; | |
if ( | |
(this.startTimeValue > 0) & | |
(this.endTimeValue > 0) & | |
(this.playbackSpeedValue != 1) & | |
(this.startTimeValue < this.endTimeValue) | |
) { | |
this.urlValue = `${this.urlValue}&start=${this.startTimeValue}&end=${this.endTimeValue}&speed=${this.playbackSpeedValue}`; | |
} else if ( | |
(this.startTimeValue > 0) & | |
(this.endTimeValue > 0) & | |
(this.playbackSpeedValue == 1) & | |
(this.startTimeValue < this.endTimeValue) | |
) { | |
this.urlValue = `${this.urlValue}&start=${this.startTimeValue}&end=${this.endTimeValue}`; | |
} else if ( | |
(this.startTimeValue > 0) & | |
(this.endTimeValue == 0) & | |
(this.playbackSpeedValue != 1) | |
) { | |
this.urlValue = `${this.urlValue}&start=${this.startTimeValue}&speed=${this.playbackSpeedValue}`; | |
} else if ( | |
(this.startTimeValue == 0) & | |
(this.endTimeValue == 0) & | |
(this.playbackSpeedValue != 1) | |
) { | |
this.urlValue = `${this.urlValue}&speed=${this.playbackSpeedValue}`; | |
} else if (this.startTimeValue > 0) { | |
this.urlValue = `${this.urlValue}&start=${this.startTimeValue}`; | |
} | |
this.sourceTarget.value = this.urlValue; | |
} | |
changeValue() { | |
this.playbackSpeedValue = this.playbackSpeedTarget.value; | |
this.startTimeValue = this.parseTime(this.startTimeTarget.value); | |
this.endTimeValue = this.parseTime(this.endTimeTarget.value); | |
this.urlValueUpdate(); | |
this.sourceTarget.value = this.urlValue; | |
history.pushState( | |
{}, | |
"", | |
`watch?v=${this.data.get("videoId")}&start=${this.startTimeValue}&end=${ | |
this.endTimeValue | |
}&speed=${this.playbackSpeedValue}` | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<% content_for :meta_title, "#{primary_title(@video.display.dancer_names, | |
@video.title, | |
@video.display.any_song_attributes, | |
@video.youtube_id)}" %> | |
<% content_for :meta_description, "#{@video.display.any_song_attributes}" %> | |
<% content_for :meta_image, "https://img.youtube.com/vi/#{@video.youtube_id}/hqdefault.jpg" %> | |
<%= render 'shared/header' %> | |
<div data-controller="hotkeys" data-hotkeys-bindings-value='{ | |
"space": "#hotkey->youtube#playPause", | |
"shift + 1": "#hotkey->youtube#setTime1", | |
"shift + 2": "#hotkey->youtube#setTime2", | |
"backspace": "#hotkey->youtube#reset", | |
"m": "#hotkey->youtube#toggleMute", | |
"+": "#hotkey->youtube#increaseVolume", | |
"-": "#hotkey->youtube#decreaseVolume", | |
"f": "#hotkey->youtube#playFullscreen ", | |
"left": "#hotkey->youtube#seekBackward", | |
"right": "#hotkey->youtube#seekForward", | |
"shift + . ": "#hotkey->youtube#increasePlaybackRate", | |
"shift + , ": "#hotkey->youtube#decreasePlaybackRate"}'></div> | |
<div id="hotkey" data-controller="youtube" | |
data-youtube-video-id-value="<%= @video.youtube_id %>" | |
data-youtube-start-seconds-value="<%= @start_value %>" | |
data-youtube-end-seconds-value="<%= @end_value %>" | |
data-youtube-playback-speed-value="<%= @playback_speed %>"> | |
<div class="video-responsive-background"> | |
<div class="video-responsive-container"> | |
<div class="video-responsive" | |
> | |
<div data-youtube-target="frame"></div> | |
</div> | |
</div> | |
</div> | |
<%= turbo_frame_tag dom_id(@video) do %> | |
<div class="video-info-container"> | |
<%= render partial: "videos/show/video_info_primary" %> | |
<div class="video-info-container-secondary"> | |
<div class="video-info-details-main"> | |
<%= render partial: "videos/show/video_info_details" %> | |
<div class="container"> | |
<h3>Comments</h3> | |
<div id="comments"> | |
<% if user_signed_in? %> | |
<%= render partial: "comments/form", locals: { commentable: @video } %> | |
<% if params[:comment] %> | |
<p>Single comment thread. <%= link_to "View all comments", url_for() %></p> | |
<% end %> | |
<% else %> | |
<div style="margin-bottom: 16px; font-size: 12px;"> | |
<%= link_to "Sign up", new_user_registration_path, style: "font-size: 0.8rem", 'data-turbo-frame': "_top" %> or <%= link_to "Login", new_user_session_path, style: "font-size: 12px;", 'data-turbo-frame': "_top" %> to reply | |
</div> | |
<% end %> | |
<%= render @comments, continue_thread: 5 %> | |
<% if @yt_comments.present? %> | |
<div id="youtube-comments"> | |
<h3>Youtube Comments</h3> | |
<% @yt_comments.each do |comment| %> | |
<%= render partial: "yt_comments/comment", locals: { comment: comment } %> | |
<% end %> | |
</div> | |
<% end %> | |
</div> | |
</div> | |
</div> | |
<% if @video.song.present? %> | |
<%= render partial: "videos/show/lyrics" if @video.song.lyrics.present? %> | |
<% end %> | |
<div class="recommended-videos-section"> | |
<% unless @videos_from_this_performance.empty? %> | |
<div class="recommended-videos-card" data-toggle-target="recommendedPerformanceVideos"> | |
<div class="recommended-videos__header"> | |
<h2>Videos from this Performance</h2> | |
</div> | |
<%= render partial: "videos/show/recommended_videos", locals: { videos: @videos_from_this_performance } %> | |
</div> | |
<% if @videos_from_this_performance.size > 3 %> | |
<div class="show-more-container"> | |
<%= button_tag type: "button", | |
class: "button", | |
style: "width: 100%;", | |
data: { action: "toggle#toggleRecommendedPerformanceVideos" } do %> | |
<%= fa_icon "angle-down", data: { 'toggle-target': "recommendedPerformanceVideosDownButton" } %> | |
<%= fa_icon "angle-up", class: "isHidden", data: { 'toggle-target': "recommendedPerformanceVideosUpButton" } %> | |
<% end %> | |
</div> | |
<% end %> | |
<% end %> | |
<% unless @videos_with_same_event.empty? %> | |
<div class="recommended-videos-card" data-toggle-target="recommendedEventVideos"> | |
<div class="recommended-videos__header"> | |
<h2>Videos from <%= @video.event.title %></h2> | |
</div> | |
<%= render partial: "videos/show/recommended_videos", locals: { videos: @videos_with_same_event } %> | |
</div> | |
<% if @videos_with_same_event.size > 3 %> | |
<div class="show-more-container"> | |
<%= button_tag type: "button", | |
class: "button", | |
style: "width: 100%;", | |
data: { action: "toggle#toggleRecommendedEventVideos" } do %> | |
<%= fa_icon "angle-down", data: { 'toggle-target': "recommendedEventVideosDownButton" } %> | |
<%= fa_icon "angle-up", class: "isHidden", data: { 'toggle-target': "recommendedEventVideosUpButton" } %> | |
<% end %> | |
</div> | |
<% end %> | |
<% end %> | |
<% unless @videos_with_same_song.empty? %> | |
<div class="recommended-videos-card" data-toggle-target="recommendedSongVideos"> | |
<div class="recommended-videos__header"> | |
<h2>Videos with <br> | |
<i>"<%= @video.song.title.titleize %>" </i><%= "by #{@video.song.artist.titleize}" %></h2> | |
</div> | |
<%= render partial: "videos/show/recommended_videos", locals: { videos: @videos_with_same_song } %> | |
</div> | |
<% if @videos_with_same_song.size > 3 %> | |
<div class="show-more-container"> | |
<%= button_tag type: "button", | |
class: "button", | |
style: "width: 100%;", | |
data: { action: "toggle#toggleRecommendedSongVideos" } do %> | |
<%= fa_icon "angle-down", data: { 'toggle-target': "recommendedSongVideosDownButton" } %> | |
<%= fa_icon "angle-up", class: "isHidden", data: { 'toggle-target': "recommendedSongVideosUpButton" } %> | |
<% end %> | |
</div> | |
<% end %> | |
<% end %> | |
<% unless @videos_with_same_channel.empty? %> | |
<div class="recommended-videos-card" data-toggle-target="recommendedChannelVideos"> | |
<div class="recommended-videos__header"> | |
<h2>Videos from <%= @video.channel.title %></h2> | |
</div> | |
<%= render partial: "videos/show/recommended_videos", locals: { videos: @videos_with_same_channel } %> | |
</div> | |
<% if @videos_with_same_channel.size > 3 %> | |
<div class="show-more-container"> | |
<%= button_tag type: "button", | |
class: "button", | |
style: "width: 100%;", | |
data: { action: "toggle#toggleRecommendedChannelVideos" } do %> | |
<%= fa_icon "angle-down", data: { 'toggle-target': "recommendedChannelVideosDownButton" } %> | |
<%= fa_icon "angle-up", class: "isHidden", data: { 'toggle-target': "recommendedChannelVideosUpButton" } %> | |
<% end %> | |
</div> | |
<% end %> | |
<% end %> | |
</div> | |
</div> | |
</div> | |
<% end %> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="under-video-container"> | |
<div class="video-info-primary-container"> | |
<div class="show-video-title"> | |
<h1> | |
<%= primary_title(@video.display.dancer_names, | |
@video.title, | |
@video.display.any_song_attributes, | |
@video.youtube_id) %> | |
</h1> | |
</div> | |
<div class="show-video-song"> | |
<h2> | |
<%= link_to_song( @video.display.el_recodo_attributes, | |
@video.display.external_song_attributes, | |
@video) %> | |
</h2> | |
</div> | |
<div class="show-video-event"> | |
<%= link_to @video.event.title.titleize, | |
root_path(event_id: @video.event.id), | |
{ "data-turbo-frame": "_top" } if @video.event.present? %> | |
</div> | |
<div class="show-video-channel"> | |
<%= link_to image_tag(@video.channel.thumbnail_url, | |
class: "channel-icon"), | |
root_path(channel: @video.channel.channel_id), { "data-turbo-frame": "_top" } if @video.channel.thumbnail_url.present? %> | |
<%= link_to truncate(@video.channel.title, | |
length: 45, omission: ""), | |
root_path(channel: @video.channel.channel_id), | |
{ class: "channel-title", "data-turbo-frame": "_top" } %> | |
</div> | |
<div class="show-video-metadata"> | |
<%= formatted_metadata(@video) %> | |
</div> | |
</div> | |
<div class="share-container" | |
data-controller="clipboard" | |
data-clipboard-success-content="Copied!" | |
data-clipboard-video-id="<%= @video.youtube_id %>" | |
data-clipboard-root-url="<%= @root_url %>" | |
> | |
<div class="youtube-controls"> | |
<%= render "videos/show/vote" %> | |
<div> | |
<%= text_field_tag 'start_time_value', @start_value.present? ? Time.at(@start_value.to_i).utc.strftime("%_M:%S") : "", placeholder: '0:00', class: "input", style: "width: 60px;", data: { action: "keyup->clipboard#changeValue input->youtube#updateStartTime", | |
"clipboard-target": "startTime", | |
"youtube-target": "startTime" } %> | |
</div> | |
<span class="spacer">-</span> | |
<div> | |
<%= text_field_tag 'end_time_value', @end_value.present? ? Time.at(@end_value.to_i).utc.strftime("%_M:%S") : "", placeholder: '0:00', class: "input", style: "width: 60px;", data: { action: "keyup->clipboard#changeValue input->youtube#updateEndTime", | |
"clipboard-target": "endTime", | |
"youtube-target": "endTime" } %> | |
</div> | |
<span class="spacer">:</span> | |
<%= select_tag :playback_speed, options_for_select( { ".25x" => "0.25", | |
".5x" => "0.50", | |
".75x" => "0.75", | |
"1x" => "1.00", | |
"1.25x" => "1.25", | |
"1.5x" => "1.50", | |
"1.75x" => "1.75", | |
"2x" => "2.00" }, sprintf('%.2f', @playback_speed.to_i)), | |
data: { action: "change->clipboard#changeValue change->youtube#updatePlaybackSpeed", | |
"clipboard-target": "playbackSpeed", | |
"youtube-target": "playbackSpeed" } %> | |
</div> | |
<div class="copy-to-clipboard"> | |
<%= text_field_tag :url, "tangotube.tv/watch?v#{@video.youtube_id}", | |
readonly: "readonly", | |
class: "copy-to-clipboard__field", | |
data: { "clipboard-target": "source" } %> | |
<%= button_tag type: "button", | |
name: "button", | |
class: "copy-to-clipboard__button button", | |
data: { "clipboard-target": "button", | |
action: "click->clipboard#copy"} do %> | |
<%= fa_icon 'share', text: "Share" %> | |
<% end %> | |
</div> | |
</div> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Controller } from "@hotwired/stimulus"; | |
import YouTubePlayer from "youtube-player"; | |
export default class extends Controller { | |
static values = { | |
videoId: String, | |
startSeconds: Number, | |
endSeconds: Number, | |
playbackSpeed: Number, | |
}; | |
static targets = ["frame", "playbackSpeed", "startTime", "endTime"]; | |
connect() { | |
var playerConfig = { | |
videoId: this.videoIdValue, | |
playerVars: { | |
autoplay: 0, // Auto-play the video on load | |
controls: 1, // Show pause/play buttons in player | |
modestbranding: 1, // Hide the Youtube Logo | |
fs: 1, // Hide the full screen button | |
cc_load_policy: 0, // Hide closed captions | |
iv_load_policy: 3, // Hide the Video Annotations | |
start: this.startSecondsValue, | |
end: this.endSecondsValue, | |
}, | |
}; | |
const player = YouTubePlayer(this.frameTarget, playerConfig); | |
player.on("ready", (e) => { | |
this.element.setAttribute("data-duration", e.target.getDuration()); | |
this.youtube = e.target; | |
this.element.setAttribute("data-time", this.time); | |
this.element.setAttribute("data-state", -1); | |
}); | |
player.setPlaybackRate(this.playbackSpeedValue); | |
player.on("playbackRateChange", (e) => { | |
this.playbackSpeedTarget.value = parseFloat(this.player.getPlaybackRate()) | |
.toFixed(2) | |
.toString(); | |
}); | |
player.on("stateChange", (e) => { | |
this.element.setAttribute("data-state", e.data); | |
this.element.setAttribute("data-time", this.time); | |
this.element.setAttribute("data-playbackRate", this.playbackRateValue); | |
this.element.setAttribute("data-volume", this.volume); | |
e.data === 1 ? this.startTimer() : clearInterval(this.timer); | |
if (e.data === YT.PlayerState.ENDED) { | |
this.player.seekTo(this.startSecondsValue); | |
} | |
}); | |
} | |
updatePlaybackSpeed() { | |
this.player.setPlaybackRate(parseFloat(this.playbackSpeedTarget.value)); | |
} | |
updateStartTime() { | |
var startTimeArray = this.startTimeTarget.value.split(":"); | |
if (startTimeArray.length == 2) { | |
var startTime = +startTimeArray[0] * 60 + +startTimeArray[1]; | |
} | |
if (startTimeArray.length == 1) { | |
var startTime = startTimeArray[0]; | |
} | |
this.startSecondsValue = startTime; | |
this.player.loadVideoById({ | |
videoId: this.videoIdValue, | |
startSeconds: this.startSecondsValue, | |
endSeconds: this.endSecondsValue, | |
}); | |
} | |
updateEndTime() { | |
var endTimeArray = this.endTimeTarget.value.split(":"); | |
if (endTimeArray.length == 2) { | |
var endTime = +endTimeArray[0] * 60 + +endTimeArray[1]; | |
} | |
if (endTimeArray.length == 1) { | |
var endTime = endTimeArray[0]; | |
} | |
this.endSecondsValue = endTime; | |
this.player.loadVideoById({ | |
videoId: this.videoIdValue, | |
startSeconds: this.startSecondsValue, | |
endSeconds: this.endSecondsValue, | |
}); | |
} | |
disconnect() { | |
document.removeEventListener("turbo:before-cache", this.player.destroy); | |
} | |
startTimer() { | |
this.timer = setInterval(() => { | |
this.element.setAttribute("data-time", this.time); | |
this.element.dispatchEvent( | |
new CustomEvent("youtube", { | |
bubbles: false, | |
cancelable: false, | |
detail: { time: this.time }, | |
}) | |
); | |
}, 1000); | |
} | |
playPause(event) { | |
event.preventDefault(); | |
var playerState = this.element.getAttribute("data-state"); | |
if (playerState == 5 || playerState == 2 || playerState == -1) { | |
this.play(); | |
} else { | |
this.pause(); | |
} | |
} | |
setTime1(event) { | |
event.preventDefault(); | |
var currentTime = this.element.getAttribute("data-time"); | |
this.startSecondsValue = currentTime; | |
this.startTimeTarget.value = currentTime; | |
this.player.loadVideoById({ | |
videoId: this.videoIdValue, | |
startSeconds: this.startSecondsValue, | |
endSeconds: this.endSecondsValue, | |
}); | |
} | |
setTime2(event) { | |
event.preventDefault(); | |
var currentTime = this.element.getAttribute("data-time"); | |
this.endSecondsValue = currentTime; | |
this.endTimeTarget.value = currentTime; | |
this.player.loadVideoById({ | |
videoId: this.videoIdValue, | |
startSeconds: this.startSecondsValue, | |
endSeconds: this.endSecondsValue, | |
}); | |
} | |
toggleMute(event) { | |
event.preventDefault(); | |
if (this.player.isMuted()) { | |
this.unMute(); | |
} else { | |
this.mute(); | |
} | |
} | |
reset(event) { | |
event.preventDefault(); | |
var currentTime = this.time; | |
this.player.loadVideoById({ | |
videoId: this.videoIdValue, | |
startSeconds: currentTime, | |
}); | |
this.endSecondsValue = ""; | |
this.endTimeTarget.value = ""; | |
this.startSecondsValue = ""; | |
this.startTimeTarget.value = ""; | |
this.player.setPlaybackRate(1); | |
this.playbackSpeedTarget == 1; | |
} | |
playFullscreen() { | |
this.play(); //won't work on mobile | |
var iframe = this.frameTarget; | |
var requestFullScreen = | |
iframe.requestFullScreen || | |
iframe.mozRequestFullScreen || | |
iframe.webkitRequestFullScreen; | |
if (requestFullScreen) { | |
requestFullScreen.bind(iframe)(); | |
} | |
} | |
increasePlaybackRate(event) { | |
event.preventDefault(); | |
this.player.setPlaybackRate(this.playbackRate + 0.25); | |
} | |
decreasePlaybackRate(event) { | |
event.preventDefault(); | |
this.player.setPlaybackRate(this.playbackRate - 0.25); | |
} | |
seekForward(event) { | |
event.preventDefault(); | |
this.seek(this.time + 5); | |
} | |
seekBackward(event) { | |
event.preventDefault(); | |
this.seek(this.time - 5); | |
} | |
play = () => this.player.playVideo(); | |
pause = () => this.player.pauseVideo(); | |
stop = () => this.player.stopVideo(); | |
mute = () => this.player.mute(); | |
unMute = () => this.player.unMute(); | |
seek = (seconds) => this.player.seekTo(seconds); | |
// playbackRate = (rate) => this.player.setPlaybackRate(rate); | |
get player() { | |
return this.youtube; | |
} | |
get time() { | |
return Math.round(this.player.getCurrentTime()); | |
} | |
get playbackRate() { | |
return this.player.getPlaybackRate(); | |
} | |
get duration() { | |
return this.player.getDuration(); | |
} | |
get state() { | |
return this.element.getAttribute("data-state"); | |
} | |
get loaded() { | |
return this.player.getVideoLoadedFraction(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment