Play mp4 video via javascript - javascript

I have a responsive site that contains html5 video. I have some javascript that checks if the size of the video elements are below a certain threshold. If so, it removes the controls, places a video play button overlay image on top of the video element then adds a click event to the container holding the video element. When the container is clicked on, it copies the video into a modal dialog box and plays the video.
Here's the quandary:
The webm version has no problems whatsoever.
The mp4 version of the modal view has no problems in Safari.
If the mp4 plays in place (i.e. is large enough to not need a modal window) it plays fine.
The mp4 version of the modal view won't play in Chrome or IE.
However, it will work in Chrome or IE if I have their built-in DOM
inspectors open (e.g. IE's F12 tools).
This can be seen here.
Here's the HTML:
<div class="video modal-trigger col-lg-4 col-md-4 col-sm-4">
<canvas></canvas>
<video preload="auto" controls="controls" poster="img/why-autologel-poster.png">
<source src="media/why-autologel.m4v" type='video/mp4'>
<source src="media/why-autologel.webm" type='video/webm'>
</video>
</div>
<div class="col-lg-8 col-md-8 col-sm-7">
<h2 class="modal-heading">
Why AutoloGel?
</h2>
<p class="modal-copy">
See what AutoloGel offers to your patients.
</p>
</div>
<!-- Modal Window -->
<div class="modal fade" id="modal-window" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="myModalLabel"></h4>
</div>
<div class="modal-body">
<div class="media"></div>
<div class="copy"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
Here's the javascript:
$(document).ready(function() {
// Play very small videos in modal box
if ( $(window).width() > 750 ) {
var modalvideo = document.getElementsByTagName('video');
// Hide controls for very small videos
for (var i = 0; i < modalvideo.length; i++) {
if ( $(modalvideo[i]).width() < 470 ) {
$(modalvideo[i]).removeAttr('controls');
if ( $('html').hasClass('IE-9') ) {
$(modalvideo[i]).after('<img class="poster-overlay" src="img/poster-overlay-ie9.png" alt="play video">');
} else {
$(modalvideo[i]).after('<img class="poster-overlay" src="img/poster-overlay.png" alt="play video">');
}
}
}
// Add click event to video container that brings up video in a modal window
$('.modal-trigger').on("click", function() {
if ( $(this).width() < 470 ) {
// Get video, title and any copy text
var media = $(this).html();
var title = $(this).next().children('.modal-heading').text();
var copy = $(this).next().children('.modal-copy').text();
// Insert video, title and copy text into modal window
$('.modal-title').html(title);
$('.modal-body > .media').html(media);
$('.modal-body > .copy').text(copy);
$('#modal-window .poster-overlay').remove('');
$('#modal-window').modal('show');
// Autoplay video after modal window has rendered
$('#modal-window').on('shown.bs.modal', function() {
modalvideo[modalvideo.length - 1].setAttribute('controls', 'controls');
modalvideo[modalvideo.length - 1].play();
});
// Stop play video when modal window is closed
$('#modal-window').on('hide.bs.modal', function() {
modalvideo[modalvideo.length - 1].pause();
});
}
});
}
});
Thanks for your help!

Figured it out.
The problem was in two parts. For Chrome, there’s some quirk with its cache and copied DOM elements. I figured it was working when the developer tools were open because the cache is disabled. Simply applying a random GET variable at the end of the src attribute for the copied video element to flag it as a different file than the one cached solved the problem.
With IE it was (is) a little different. HubSpot uses Amazon S3 as its CDN and when I looked at the header for the video file its content type was set as application/octet-stream which Internet Explorer doesn’t support. AWS allows this to be set when the file is uploaded but HubSpot is doing this behind the scenes with no user ability to set this that I’m aware of. They're working on a fix.
The solution that ended up working:
$(document).ready(function() {
// Play very small videos in modal box
if ( $(window).width() > 750 ) {
var allvideos = $('video');
// Hide controls for very small videos
for (var i = 0; i < allvideos.length; i++) {
if ( $(allvideos[i]).width() < 470 ) {
$(allvideos[i]).removeAttr('controls');
if ( $('html').hasClass('IE-9') ) {
$(allvideos[i]).after('<img class="poster-overlay" src="img/poster-overlay.png" alt="play video">');
} else {
$(allvideos[i]).after('<img class="poster-overlay" src="img/poster-overlay.png" alt="play video">');
}
}
}
// Add click event to video container that brings up video in a modal window
$('.modal-trigger').on('click', function() {
if ( $(this).width() < 470 ) {
// Get video/img, title and any copy text
var media = $(this).html();
var title = $(this).next().children('.modal-heading').text();
var copy = $(this).next().children('.modal-copy').text();
if (! title.length) { title = '<br>'; }
// Insert video, title and copy text into modal window
var modalsrc = [];
var modaltype = [];
$(media).children('source').each(function() {
modalsrc.push( $(this).attr('src') );
modaltype.push( $(this).attr('type') );
});
$('.modal-title').html(title);
$('.modal-body > .media').html(media);
$('.modal-body > .copy').text(copy);
$('#modal-window .poster-overlay').remove('');
// Assign a random version to video src to bypass cache
var modalsources = $('#modal-window source');
var nocachesrc = '';
for (var i = 0; i < modalsources.length; i++) {
nocachesrc = modalsrc[i] + '?rnd=' + Math.random()*Math.random();
modalsources[i].setAttribute('src', nocachesrc);
modalsources[i].setAttribute('type', modaltype[i]);
}
var modalvideo = $('#modal-window video');
modalvideo[0].setAttribute('controls', 'controls');
// Reveal modal window and play video
$('#modal-window').modal('show');
$('#modal-window').on('shown.bs.modal', function() {
modalvideo[0].play();
});
// Stop playing video when modal window is closed
$('#modal-window').on('hide.bs.modal', function() {
modalvideo[0].pause();
});
}
});
}
});

Remove the semicolon from the type attribute of the source nodes, should be: type="video/mp4", other browsers are probably just being forgiving of this.

Related

Migrate to Bootstrap 5 - Video Modal

I have a modal with video inside, written for Bootstrap 4. I want to migrate this to Bootstrap 5, where no jQuery is available. How may I do this? The problem is the JavaScript here:
<script>
// codepen.io/JacobLett/pen/xqpEYE
$(document).ready(function() {
var $videoSrc;
$('.video-btn').click(function() {
$videoSrc = $(this).data( "src" );
});
console.log($videoSrc);
// when the modal is opened autoplay it
$('#videoModal').on('shown.bs.modal', function (e) {
// set the video src to autoplay and not to show related video. Youtube related video is like a box of chocolates... you never know what you're gonna get
$("#video").attr('src',$videoSrc + "?autoplay=1&modestbranding=1&showinfo=0" );
})
// stop playing the youtube video when I close the modal
$('#videoModal').on('hide.bs.modal', function (e) {
// a poor man's stop video
$("#video").attr('src',$videoSrc);
})
});
</script>
The modal is using
<button type="button" class="video-btn" data-bs-toggle="modal" data-bs-target="#videoModal" data-bs-src="https://www.youtube.com/embed/EzDC8aAJln0" >
<img src="/img/video/dbschema-video.svg" class="img-fluid" alt="DbSchema Video Presentation">
</button>
<div class="modal fade" id="videoModal" tabindex="-1" role="dialog" aria-labelledby="dbschemaModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<div class="ratio ratio-16x9">
<iframe class="embed-responsive-item" src="" id="video" allowscriptaccess="always" allow="autoplay"></iframe>
</div>
</div>
</div>
</div>
</div>
See this.
So you can do something like below:
$(document).ready(function() {
var $videoSrc;
document.querySelector('.video-btn').addEventListener('click', function() {
$videoSrc = $(this).data( "src" );
});
console.log($videoSrc);
// when the modal is opened autoplay it
document.getElementById('videoModal').addEventListener('shown.bs.modal', function (e) {
// set the video src to autoplay and not to show related video. Youtube related video is like a box of chocolates... you never know what you're gonna get
$("#video").attr('src',$videoSrc + "?autoplay=1&modestbranding=1&showinfo=0" );
})
// stop playing the youtube video when I close the modal
document.getElementById('videoModal').addEventListener('hide.bs.modal', function (e) {
// a poor man's stop video
$("#video").attr('src',$videoSrc);
})
});
Also you should add -bs- to your data-attributes wherever you use bootstrap-5
For vanilla JS it would be something like this...
var videoBtn = document.querySelector('.video-btn')
var videoModal = document.getElementById('videoModal')
var video = document.getElementById('video')
var videoSrc
videoBtn.addEventListener('click',function(e){
videoSrc = videoBtn.getAttribute('data-bs-src')
})
videoModal.addEventListener('shown.bs.modal',(e)=>{
video.setAttribute('src', videoSrc + '?autoplay=1&modestbranding=1&showinfo=0')
})
videoModal.addEventListener('hide.bs.modal',(e)=>{
video.setAttribute('src', videoSrc)
})
Here's the working code

Opening of modal requiring to be opened two times

In my website I have a problem with my modal. It is supposed to be triggered by an icon appearing close to the embed tags. Although it works to activate the modal, it just displays the modal-content without anything in it; after closing the modal and making a second click on the openModal() function the wanted embed file is displayed. Then when clicking another document it displays the preceding document until closed and clicked again. Why is this happening and what should I do to fix it?
HTML (but with only one embed) :
<div class = "overlay-cont">
<embed src="link-to-pdf.pdf" >
<div class = "overlay">
<img class = "enlarge-icon" src = "arrow-icon.svg" onclick ="openModal(0)">
</div>
</div>
<div id = "myModal" class = "modal">
<span class="close cursor" onclick="closeModal()">×</span>
<div class="modal-content">
<embed id = "currentDoc">
</div>
</div>
JavaScript:
var doc = document.getElementsByTagName("EMBED");
function openModal(n){
document.getElementById("myModal").style.display = "block";
document.getElementById("currentDoc").src = doc[n].src;
}
function closeModal(){
document.getElementById("myModal").style.display = "none"
}
Instead of having a static embed element and giving it dynamic src attribute value, generate dynamic embed element every time you show the modal and remove it every time you close the modal.
var doc = document.getElementsByTagName("EMBED");
function openModal(n) {
var modal = document.getElementById("myModal");
modal.style.display = "block";
var node = document.createElement("embed");
var src_attr = document.createAttribute("src");
src_attr.value = doc[n].src;
node.setAttributeNode(src_attr);
modal.appendChild(node);
}
function closeModal() {
var e = document.querySelectorAll("embed")[1];
e.parentNode.removeChild(e);
document.getElementById("myModal").style.display = "none"
}
<html>
<head>
</head>
<body>
<div class="overlay-cont">
<embed src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<div class="overlay">
<img class="enlarge-icon" src="https://cdn.sstatic.net/Sites/stackoverflow/img/favicon.ico?v=4f32ecc8f43d" onclick="openModal(0)">
</div>
</div>
<div id="myModal" class="modal">
<span class="close cursor" onclick="closeModal()">×</span>
<div class="modal-content"></div>
</div>
</body>
</html>

How do I debug in chrome to get value of an observable in knockout?

I am working on a video player app and It has multiple play buttons and each time I hit a play button loggerVideoURL takes in a url I want to find the value of loggerVideoURL.
How do I debug to find the value of loggerVideoURL at each instance of hitting the play button.
Here's my code
HTML
<div class="loggerVideoControlsWrapper p0" id="video-controls-wrapper" style="">
<!-- Video -->
<div class="video-js-responsive-container vjs-hd" id="video-container">
<video crossorigin="anonymous" id="logger-media-player" preload="auto" data-bind="attr:{src:loggerVideoURL},event:{timeupdate:onPlayerTimeUpdate,play:playerOnPlay,pause:playerOnPause,seeked:playerOnSeeked,seeking:playerOnSeeking,loadedmetadata:playerOnLoadedmetadata,error:playerOnError}" class="video-js vjs-default-skin" width="auto" height="auto">
#*controls="true" - for now *#
#*<source data-bind="attr:{src:loggerVideoURL}" type="video/mp4" />*#
#*<track label="English" srclang="en" kind="captions" data-bind="attr:{src:captionsFileUrl}" />*#
</video>
<div data-bind="if: playerSeeking() || !playerLoadedMetadataFired() || playerErrorMsg()">
<div id="seeking-overlay">
<div class="snippet-demo" data-bind="if: !playerErrorMsg()" style="position: relative; top: 50%; transform: translateY(-50%);">
<div class="snippet-demo-container demo-spinner demo-spinner__spinner-default">
<!-- MDL Spinner Component -->
<div class="mdl-spinner mdl-js-spinner is-active is-upgraded" data-upgraded=",MaterialSpinner"><div class="mdl-spinner__layer mdl-spinner__layer-1"><div class="mdl-spinner__circle-clipper mdl-spinner__left"><div class="mdl-spinner__circle"></div></div><div class="mdl-spinner__gap-patch"><div class="mdl-spinner__circle"></div></div><div class="mdl-spinner__circle-clipper mdl-spinner__right"><div class="mdl-spinner__circle"></div></div></div><div class="mdl-spinner__layer mdl-spinner__layer-2"><div class="mdl-spinner__circle-clipper mdl-spinner__left"><div class="mdl-spinner__circle"></div></div><div class="mdl-spinner__gap-patch"><div class="mdl-spinner__circle"></div></div><div class="mdl-spinner__circle-clipper mdl-spinner__right"><div class="mdl-spinner__circle"></div></div></div><div class="mdl-spinner__layer mdl-spinner__layer-3"><div class="mdl-spinner__circle-clipper mdl-spinner__left"><div class="mdl-spinner__circle"></div></div><div class="mdl-spinner__gap-patch"><div class="mdl-spinner__circle"></div></div><div class="mdl-spinner__circle-clipper mdl-spinner__right"><div class="mdl-spinner__circle"></div></div></div><div class="mdl-spinner__layer mdl-spinner__layer-4"><div class="mdl-spinner__circle-clipper mdl-spinner__left"><div class="mdl-spinner__circle"></div></div><div class="mdl-spinner__gap-patch"><div class="mdl-spinner__circle"></div></div><div class="mdl-spinner__circle-clipper mdl-spinner__right"><div class="mdl-spinner__circle"></div></div></div></div>
</div>
</div>
<!-- ko if: playerErrorMsg() -->
<div data-bind="" class="alert alert-danger alert-dismissable" style="width: 75%;margin: auto auto;position: relative; top: 50%; transform: translateY(-50%);">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<h4><i class="fa fa-times-circle"></i> Error</h4> <span data-bind="text:playerErrorMsg" style="font-weight: normal"></span>
</div>
<!-- /ko -->
</div>
</div>
</div>
JavaScript
function ClsLoggerVideo(parent) {
var self = this;
var timer;
var timerInterval = 800;
var FrameRate = TimecodeMath.FrameRate;
function log(msg) { console.log(msg); }
$(document).on('mouseup', function () { clearInterval(timer); });
self.isIe = vmsWebUtils.isIE();
self.parentLogger = ko.observable(parent);
self.captionFiles = ko.observableArray();
self.loggerVideoURL = ko.observable();
//self.captionsFileUrl = ko.observable('http://ny-venturae-7.viacom_corp.ad.viacom.com/video/APOCALYPSE_NOW_1080p2398_CLIP.vtt');
self.captionFiles.push();
self.techAttributes = ko.observable(null);
self.frameRate = ko.computed(function () { return self.techAttributes() == null ? null : self.techAttributes().frameRate; });
self.isDropFrame = ko.computed(function () {
return self.techAttributes() == null ? null : self.techAttributes().isDropFrame;
});
self.startTcInFrames = ko.computed(function () {
return self.techAttributes() == null ? null : self.techAttributes().startTcInFrames;
});
I'm not sure on whether you're asking how to debug in Chrome, or how to get the value in Knockout, so I'll throw out some info and hope it answers your question.
Debugging in Chrome:
Open the developer tools (More Tools > Developer Tools).
Go to the Sources tab.
In the left window pane open the folder that contains your Javascript files (may be called "js" for example, but will depend on your folder naming structure in your web app).
Click on the .js file that contains your Knockout viewmodel definition. In this case that would be the one that contains the function ClsLoggerVideo.
Then find where you want to set a breakpoint in the .js file and simply click on the line number.
When you run the function again it will break at that spot and you can hover over values or add watches by highlighting right clicking a variable. In this case loggerVideoURL.
Keep in mind with Knockout, you need to make the watch like this: self.loggerVideoURL()
You need to include the parentheses to get the underlying value of the observable. If you don't include them the you'll just see the KO observable function definition.
As far as your code is concerned I'm seeing multiple issues. First, I don't see where you handle the Play button press. You could do this any number of ways in Javascript, but since you're using KO I'd suggest using a click event binding like this:
<button type="button" data-bind="click: playButtonClicked">Play</button>
Then in your viewmodel function ClsLoggerVideo, put this:
self.playButtonClicked = function () {
//Put some code here to land a breakpoint on so you can see the value of loggerVideoURL()
}
I hope that helps.
The Knockout Context Debugger chrome extension is extremely helpful. You can open dev tools as usual, but there is a new subtab for inspecting bound elements and the backing viewmodel. This approach doesn't require any breakpoints.

Embedded Youtube videos not playing in mobile iOS Safari

Recently we've found a bug that exists only in Safari on iPhone(s) however I cannot pinpoint the source of the issue. When pressing the play button the video appears to load but then closes immediately.
All answers I've found so far aren't recent and/or don't solve the problem. Testing in BrowserStack is giving me this error: Invalid CSS property declaration at: * from the www-embed-player-sprite-mode-vfl9mHoaB.css file served from Youtube.
I'm also open to optional ways of handling embedded videos to avoid this issue.
The code:
#set($showVideo = $request.getParameter("showVideo"))
#set($howItWorksID = $placeholder.getAttributeValueGroup().getAttributeValue('product_howitworks', $sesShoppingCart.getLocale()))
#set($embeddedURL = "https://www.youtube.com/embed/" + $howItWorksID + "?rel=0")
#set($hasVideoID = false)
#if( $howItWorksID && $howItWorksID != "" )
#set( $hasVideoID = true )
#end
<div id="howItWorksModal" class="modal-howItWorks modal fade">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<button type="button" class="close js-modalClose close--howItWorks" data-dismiss="modal">
<span aria-hidden="true">×</span>
<span class="sr-only">close</span>
</button>
<div>
<div class="prose howItWorks-embedVideoWrapper">
<div class="iframe-container">
<iframe id="howItWorks-ModalVersion" class="howItWorks js-howItWorks-iframe" width="100%" src="" frameborder="0" allowfullscreen preload></iframe>
</div>
</div>
</div>
</div>
</div>
</div>
jQueryReady(function() {
var videoURL = "$embeddedURL";
// Load destination video and autoplay when modal opens
$("#howItWorksModal").on('shown.bs.modal', function(ev) {
ev.preventDefault();
$("#howItWorks-ModalVersion").attr("src", videoURL + "&autoplay=1");
console.log('clicked on');
});
// Kill video when modal is closed
$('#howItWorksModal').on('hidden.bs.modal', function (e) {
$('.js-howItWorks-iframe').each( function(){
$(this).attr('src', '');
});
});
// Kill mobile video if playing while window is resized
function mobileVideoSource(){
var mobileBlock = $('#BuyNow-mobileBlock'),
howToVid_mobile = $('#howItWorks-MobileVersion');
if (mobileBlock.is(":hidden")) {
// if mobile block is hidden remove it's source
howToVid_mobile.attr('src', '');
} else {
// if mobile block is displayed add a source
howToVid_mobile.attr('src', videoURL);
}
}
sdi.window.on('resize', mobileVideoSource);
})
mobileVideoSource() was being called and removing the src when the video was opening. I did not investigate into why this was happening, however I restructured the script to completely remove the iframe instead of the src and inject a new instance if (for whatever rare edge use case) the screen is resized back down to mobile and the user wants to watch the video again:
function mobileVideoSource() {
var mobileBlock = $('#BuyNow-mobileBlock'),
currentVid = $('#howItWorks-MobileVersion'),
videoContainer = $('.iframe-container')
new_iframe = $('<iframe id="howItWorks-MobileVersion" class="howItWorks" width="560" height="315" src="$embeddedURL" frameborder="0" allowfullscreen></iframe>');
if (mobileBlock.is(":hidden")) {
currentVid.remove();
} else {
videoContainer.append(new_iframe);
}
}

Adding html around videos prevents Vimeo api from working

i got a page on a site and there are several videos within blocs. When i play one video and then hit the close button or click on another box to open it, it works all just fine. But the problem comes when i add extra html markup around the videos, it breaks the close button behavior.
Here's the markup of a box (i've got multiples in my page):
<div class="box" data-size="660,605">
<div class="cunload2">
<div class="expandable">
<span class="simple">
<a class="cunload" href="javascript:;"></a>
</span>
<div class="exandable-vids-container">
<iframe ... ></iframe>
</div>
</div>
</div>
</div>
The cunload button is the one closing the box and unloading the video.
Here's the javascript (as seing at the vimeo api's page):
(function(){
var vimeoPlayers = document.querySelectorAll('iframe'), player;
for (var i = 0, length = vimeoPlayers.length; i < length; i++) {
player = vimeoPlayers[i];
$f(player).addEvent('ready', ready);
}
function addEvent(element, eventName, callback) {
if (element.addEventListener) { element.addEventListener(eventName, callback, false); }
else { element.attachEvent(eventName, callback, false); }
}
function ready(player_id) {
var container = document.getElementById(player_id).parentNode.parentNode,
froogaloop = $f(player_id),
apiConsole = container.querySelector('.console .output');
function setupSimpleButtons() {
var unloadBtn = container.querySelector('.simple').querySelector('.cunload');
addEvent(unloadBtn, 'click', function() { froogaloop.api('unload'); }, false);
}
setupSimpleButtons();
$(".cunload, .box:not(.expanded)").click(function(){
//if (!$(this).is('expanded')) {
froogaloop.api('unload');
//}
});
}
})();
This works just fine, but when i add an extra <div> around the iframe (and i really need to) it doesnt work anymore.
Any clue?
EDIT: problem is more complicated, i added an <ul> and <li> tags around the iframes as such:
<div class="box" data-size="660,605">
<div class="cunload2">
<div class="expandable">
<span class="simple">
<a class="cunload" href="javascript:;"></a>
</span>
<div class="exandable-vids-container">
<ul>
<li><iframe ... ></iframe></li>
<li><iframe ... ></iframe></li>
</ul>
</div>
</div>
</div>
</div>
and modified the js line :
var container = document.getElementById(player_id).parentNode.parentNode
to:
var container = document.getElementById(player_id).parentNode.parentNode.parentNode.parentNode
and i got it working.
The problem is that i use the <ul> and <li> tags to call jcarousel and that it adds an extra 3 divs between my .exandable-vids-container and the <ul>. Moreover, sometimes i'll call this carousel and sometimes not, so i really can't go with adding extras .parentNode in the js. This would have to be dynamic and i think the solution has to be in that direction.

Categories

Resources