Created
December 10, 2019 22:41
-
-
Save Exadra37/c10d7258864e85c45017292cfba764b9 to your computer and use it in GitHub Desktop.
Youtube Video Player
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
let Player = { | |
player: null, | |
init(domId, playerId, onReady){ | |
window.onYouTubeIframeAPIReady = () => { | |
this.onIframeReady(domId, playerId, onReady) | |
} | |
let youtubeScriptTag = document.createElement("script") | |
youtubeScriptTag.src = "//www.youtube.com/iframe_api" | |
document.head.appendChild(youtubeScriptTag) | |
}, | |
onIframeReady(domId, playerId, onReady) { | |
this.player = new YT.Player(domId, { | |
playerVars: { | |
// 0 - disallows full screen button | |
fs: 0, | |
// 0 - does not show youtube recommendations in the end of the video and will put it on start. | |
rel: 0 | |
}, | |
height: "100%", | |
width: "100%", | |
videoId: playerId, | |
events: { | |
"onReady": (event => onReady(event)), | |
"onStateChange": (event => this.onPlayerStateChange(event)) | |
} | |
}) | |
}, | |
onPlayerStateChange(event){ | |
}, | |
getCurrentTime(){ | |
return Math.floor(this.player.getCurrentTime() * 1000) | |
}, | |
seekTo(millsec) { | |
return this.player.seekTo(millsec / 1000) | |
}, | |
resume() { | |
return this.player.playVideo() | |
}, | |
pause() { | |
return this.player.pauseVideo() | |
} | |
} | |
export default Player |
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
<h2><%= @video.title %></h2> | |
<div class="row"> | |
<div class="col-lg-8 col-sm-12 mb-3"> | |
<div class="video-container"> | |
<div class="video-row me"> | |
<%= content_tag :div, id: "video", data: [id: @video.id, player_id: player_id(@video)] do %> | |
<% end %> | |
</div> | |
</div> | |
</div> | |
<div class="col-lg-4 col-sm-12"> | |
<div class="tab_container"> | |
<input id="tab1" type="radio" name="tabs" checked> | |
<label for="tab1" class="bg-dark"><i class="fa-icon fas fa-list-ul"></i><span>Index</span></label> | |
<input id="tab2" type="radio" name="tabs"> | |
<label for="tab2" class="bg-dark"><i class="fa-icon far fa-edit"></i><span>Notes</span></label> | |
<input id="tab3" type="radio" name="tabs"> | |
<label for="tab3" class="bg-dark"><i class="fa-icon far fa-newspaper"></i><span>Articles</span></label> | |
<input id="tab4" type="radio" name="tabs"> | |
<label for="tab4" class="bg-dark"><i class="fa-icon far fa-comments"></i><span>Chats</span></label> | |
<input id="tab5" type="radio" name="tabs"> | |
<label for="tab5" class="bg-dark"><i class="fa-icon fab fa-readme"></i><span>Reviews</span></label> | |
<section id="content1" class="tab-content"> | |
<p>Index will be here...</p> | |
</section> | |
<section id="content2" class="tab-content"> | |
<div id="take-note"> | |
<textarea id="msg-input" rows="3" class="form-control" placeholder="add a note here... markdown is supported !!!"></textarea> | |
<button id="msg-submit" class="btn btn-dark form-control" type="submit">Add </button> | |
</div> | |
<h4>Notes</h4> | |
<div class="view-notes"> | |
<div id="msg-container" class="panel-body annotations"></div> | |
</div> | |
</section> | |
<section id="content3"> | |
<div class="view-notes tab-content"> | |
<p>Articles comming soon</p> | |
</div> | |
</section> | |
<section id="content4"> | |
<div class="tab-content"> | |
<p>Chat comming soon...</p> | |
</div> | |
</section> | |
<section id="content5"> | |
<div class="tab-content"> | |
<p>Reviews comming soon...</p> | |
</div> | |
</section> | |
</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 Player from "./player" | |
let Video = { | |
init(socket, element){ | |
if (!element) { | |
return | |
} | |
let playerId = element.getAttribute("data-player-id") | |
let videoId = element.getAttribute("data-id") | |
socket.connect() | |
Player.init(element.id, playerId, () => { | |
this.onReady(videoId, socket) | |
}) | |
}, | |
onReady(videoId, socket){ | |
let msgContainer = document.getElementById("msg-container") | |
let msgInput = document.getElementById("msg-input") | |
let postButton = document.getElementById("msg-submit") | |
let vidChannel = socket.channel("videos:" + videoId) | |
msgInput.addEventListener("focusin", e => { | |
Player.pause() | |
}) | |
msgInput.addEventListener("focusout", e => { | |
Player.resume() | |
}, true) | |
postButton.addEventListener("click", e => { | |
let payload = {body: msgInput.value, at: Player.getCurrentTime()} | |
vidChannel.push("new_annotation", payload) | |
.receive("error", e => console.log(e)) | |
msgInput.value = "" | |
}) | |
vidChannel.on("new_annotation", (resp) => { | |
vidChannel.params.last_seen_id = resp.id | |
this.renderAnnotation(msgContainer, resp) | |
}) | |
msgContainer.addEventListener("click", e => { | |
e.preventDefault() | |
let seconds = e.target.getAttribute("data-seek") || | |
e.target.parentNode.getAttribute("data-seek") | |
if (!seconds) { | |
return | |
} | |
Player.seekTo(seconds) | |
}) | |
vidChannel.join() | |
.receive("ok", (resp) => { | |
let ids = resp.annotations.map(ann => ann.id) | |
if (ids.length > 0) { | |
vidChannel.params.last_seen_id = Math.max(...ids) | |
} | |
this.scheduleMessages(msgContainer, resp.annotations) | |
}) | |
.receive("error", reason => console.log("join failed", reason)) | |
}, | |
// escapes content to protect against XSS attacks. | |
esc(str){ | |
let div = document.createElement("div") | |
div.appendChild(document.createTextNode(str)) | |
return div.innerHTML | |
}, | |
parseMarkdownToHtml(str) { | |
//let converter = new showdown.Converter({extensions: ['prettify']}) | |
//converter.setFlavor('github') | |
let converter = new showdown.Converter() | |
// TODO: | |
// → we must sanitize the html we generated from the markdown to prevent XSS attacks. | |
// → still need to decide if we keep the markdown parser in client side or server side. | |
// → more info in how to mitigate can be found in https://github.com/showdownjs/showdown/wiki/Markdown's-XSS-Vulnerability-(and-how-to-mitigate-it). | |
// → this.esc(strg) function cannot be used to mitigate the XSS, once it will ignore all html we pass into it. | |
return ` | |
<div class="note-body">${converter.makeHtml(str)}</div> | |
` | |
}, | |
renderMarkdownToHtml(str) { | |
let html = this.parseMarkdownToHtml(str) | |
let div = document.createElement("div") | |
div.insertAdjacentHTML("afterbegin", html) | |
return div.innerHTML | |
}, | |
renderAnnotation(msgContainer, {user, body, at}){ | |
let template = document.createElement("div") | |
let body_html = this.renderBreadcrumb(body) || this.renderMarkdownToHtml(body) | |
template.innerHTML = ` | |
${this.renderNoteHeader(user, at)} | |
${body_html} | |
` | |
msgContainer.insertAdjacentHTML('afterbegin', template.innerHTML) | |
msgContainer.scrollTop = msgContainer.scrollHeight | |
// https://schier.co/blog/2013/01/07/how-to-re-run-prismjs-on-ajax-content.html | |
Prism.highlightAll() | |
}, | |
renderNoteHeader(user, at) { | |
return ` | |
<div class="note-header"><span><strong>${this.esc(user.username)}</strong> at </span> | |
<a href="#" data-seek="${this.esc(at)}">[${this.formatTime(at)}]</a> | |
<span class="video-at">(h:m:s)</span><span><strong>:</span></strong> | |
</div> | |
` | |
}, | |
renderBreadcrumb(body) { | |
if (body.startsWith("\/b ")) { | |
body = body.replace("/b ", "") | |
return ` | |
<p class="note-body"><strong>Breadcrumb:</strong> ${this.esc(body)}</p> | |
` | |
} | |
return "" | |
}, | |
scheduleMessages(msgContainer, annotations){ | |
setTimeout(() => { | |
let ctime = Player.getCurrentTime() | |
let remaining = this.renderAtTime(annotations, ctime, msgContainer) | |
this.scheduleMessages(msgContainer, remaining) | |
}, 1000) | |
}, | |
renderAtTime(annotations, seconds, msgContainer){ | |
return annotations.filter( ann => { | |
if (ann.at > seconds) { | |
return true | |
} else { | |
this.renderAnnotation(msgContainer, ann) | |
return false | |
} | |
}) | |
}, | |
formatTime(at){ | |
let date = new Date(null) | |
date.setSeconds(at / 1000) | |
return date.toISOString().substr(11, 8) | |
} | |
} | |
export default Video | |
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
defmodule Rumbl.VideoView do | |
use Rumbl.Web, :view | |
# TODO: | |
# → Paly Id must come from database | |
def player_id(video) do | |
~r{^.*(?:youtu\.be/|\w+/|v=)(?<id>[^#&?]*)} | |
|> Regex.named_captures(video.url) | |
|> get_in(["id"]) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment