Ember updating video sources but not being reflected by browser - javascript

I have an ember application with nested resources in which I'm showing videos. The outer resource (videos) simply displays all of the videos. When you click a video the nested video resource is activated and a title/player is shown.
This works great the first time you click something. The video shows up and it plays. HOWEVER, when clicking another video, the nested resource video is updated and the DOMs <source> gets updated but the OLD video continues to play! Why is this happening and how do I fix it?
Working Example on JSFiddle

I would use a component to create the video player, and wrap up the rerender logic in the component.
The component would look something like
App.VideoPlayerComponent = Ember.Component.extend({
src: null,
rerenderPlayer: function(){
if(this.$()) {
Ember.run.debounce(this, this.rerender, 200);
}
},
srcObserver: function(){
this.rerenderPlayer();
}.observes('src')
});
and the component template would look like
<script type="text/x-handlebars" data-template-name="components/video-player">
<video controls>
<source {{bind-attr src=src}} type = "video/mp4"></source>
</video>
</script>
You would then be able to just reference the component in your template like this
<script type="text/x-handlebars" data-template-name="video">
<h2>{{title}}</h2>
{{video-player src=src}}
</script>
You can see a working bin here: http://emberjs.jsbin.com/fehipa/2/edit
As a bonus.. If you created a video player component you would also be able to wrap up the ability to pause and play the video in the component. So that you can have a totally reusable video player to use anywhere in your app.

If you move the {{bind-attr src="src"}} from the <source> element up to the <video> element, it just works without any hacks.
your code would change from:
<video controls>
<source {{bind-attr src="src"}} type = "video/mp4"></source>
</video>
to
<video controls {{bind-attr src="src"}}></video>
Working example: http://jsfiddle.net/jfmzwhxx/

It's a bit ugly, but this can be achieved by rerendering the view. Add the following code:
App.VideoRoute = Ember.Route.extend({
oldVideoId: null,
afterModel: function (resolvedModel) {
if (this.get('oldVideoId') !== resolvedModel.id) {
this.set('oldVideoId', resolvedModel.id);
$.each(Ember.View.views, function( i, view ) {
if (view.renderedName === "videos"){
view.rerender();
}
});
}
}
});
Working Fiddle

I would take a similar approach to #AlliterativeAlice, but I would definitely not recommend doing this operation at the route level. This problem is a consequence of DOM security and thus is a job for the view. You're best bet is to setup an observer in your view class and call rerender from there. E.g.:
App.VideoView = Ember.View.extend({
_didChangeVideoSrc: function() {
// Make the view is still in the DOM
if(this.$()) {
this.rerender();
}
}.observes('controller.src')
});
I also updated your fiddle to make this work: http://jsfiddle.net/rverobx9/2/

While answers suggesting to re-render in any way might solve your problem, if you have access to the API of the player it'd be nice to see if stopping the video in the willDestroyElement of your view won't fix that.
Also you could have your video player being an Ember component, ensuring good setup, teardown and update:
App.VideoPlayerComponent = Ember.Component.extend({
source: null, // used in the template using that component
isPlayerReady: false, // used to know internally if the player is ready
setupPlayer: (function(){
// somehow setup the video player if needed
// (if Flash player for example, make the flash detection and whatever)
// once ready, trigger our `videoPlayerReady` event
this.set('isPlayerReady', true);
this.trigger('videoPlayerReady');
}).on('didInsertElement'),
updatePlayerSource: (function(){
// start playing if we have a source and our player is ready
if ( this.get('isPlayerReady') && this.get('source') )
{
// replace with the correct js to start the video
this.$('.class-of-player').videoPlay(this.get('source'));
}
}).observes('source').on('videoPlayerReady'),
teardownSource: (function(){
// stop playing if our player is ready since our source is going to change
if ( this.get('source') && this.get('isPlayerReady') )
{
// replace with the correct js to stop the player
this.$('.class-of-player').videoStop();
}
}).observesBefore('source'),
teardownPlayer: (function(){
// teardown the player somehow (do the opposite of what is setup in `setupPlayer`)
// then register that our player isn't ready
this.set('isPlayerReady', false);
}).on('willDestroyElement')
});
This will allow you to be sure everything is setup and teardown correctly, and since it's a component you can re-use, it'll be very easy to have a fallback to a flash player for example. Then whatever you have to handle and fix stuff related to the player, it will be in that component, and you'll just have to replace the part of your template with the player with:
{{video-player source=ctrl.videoUrl}}

Related

How to get/set video.currentTime via a prop in Svelte?

I am working on a Svelte component that wraps a video tag. I am trying to make it so that you can explicitly set the 'playbackTime' prop to change the currentTime of the video. However, I would also like to update this prop when the video's currentTime changes as a result of playing the video so I can show it in the user interface.
Is something like this possible?
<script>
export let playbackTime = 0;
let video;
$: playbackTime, () => {
if (propWasSetBySomethingOtherThanTheTimeupdateEvent()) {
video.currentTime = playbackTime;
}
};
</script>
<video on:timeupdate={() => playbackTime = video.currentTime} bind:this={video}>
<!-- video sources -->
</video>
Time: ${playbackTime}
I basically want to make playbackTime a proxy for video.currentTime. I realise I could expose video and get/set currentTime directly but I'd rather keep this variable private to the component.
One idea I had is to set a flag when the timeupdate event triggers and then unset it afterwards so I can tell whether the playbackTime was set by the event, but that seems prone to race condition bugs.
Thanks in advance.
Detecting where changes come from is not supported right now.
Not quite sure what you mean by keeping the variable private, you export a property that corresponds to the time, so I would recommend just using a binding like this:
<video bind:currentTime={playbackTime} ...>

AMP Audio: Track Play/Pause/Complete and Capture in Google Tag Manager

I am working with the amp-audio component for a project, and need to be able to set up tags and triggers in Google Tag Manager track when the user plays/pauses the audio, and when the audio completes. From the documentation for amp-audio, it claims that I can utilize the Audio Play and Pause triggers to track the events via amp-analytics. However, this does not work to trigger any analytics. Further, the amp-analytics documentation only lists video-* for media-related triggers, with no reference whatsoever anywhere on the page to anything related to amp-audio. This conflict of documentation is not only confusing, but frustrating since there's no way to tell which is accurate, if either.
Code snippets and trigger configuration (taken straight from the amp-audio documentation, updated with my own ids), which does not work:
<amp-audio id="my-amp-audio-player" controlsList="nodownload" height="50" width="auto">
...
</amp-audio>
...and...
"triggers": {
"audioPlay": {
"on": "audio-play",
"request": "event",
"selector": "#my-amp-audio-player"
},
"audioPause": {
"on": "audio-pause",
"request": "event",
"selector": "#my-amp-audio-player"
}
}
As a workaround, I've also tried to include and use custom JS via the amp-script component. Using this with amp-audio has proven fruitless, and I've also tried to use a straight-up audio tag in place of amp-audio. Using the pure audio tag gives me the ability to target the audio player and capture play, pause, and ended events...however, trying to do anything that would result in a successful AMP trigger ends up throwing the "terminated due to illegal mutation" error as documented in the amp-script documentation.
Code snippets for a click event trigger set up in Google Tag Manager:
Markup:
<amp-script layout="container" src="/static-assets/audio.js">
<audio id="my-amp-audio-player" controls controlsList="nodownload" height="50" width="auto">
...
</audio>
<button id="btn-audio-play-trigger">Play Trigger</button>
<button id="btn-audio-pause-trigger">Pause Trigger</button>
</amp-script>
JS:
const audioPlayer = document.getElementById('my-amp-audio-player')
audioPlayer.addEventListener('play', () => {
// Below (and other uses of this) results in illegal mutation error (if an AMP component)
// or something like 'TypeError: document.getElementById(...).click is not a function'
// when using a <button> element
document.getElementById('btn-audio-play-trigger').click()
})
audioPlayer.addEventListener('pause', () => {
document.getElementById('btn-audio-pause-trigger').click()
})
audioPlayer.addEventListener('ended', () => {
// TODO: Implement functionality as needed
})
Current Status:
No matter which implementation or combination of uses I implement, nothing I've tried has been able to successfully trigger and catch an amp-audio or audio event that Tag Manager can catch and log. Any advice, insight, or direction is appreciated!

javascript not executed with setTimeout but does with button click event

I am trying to view a video stream from an IP camera in a web page, when the stream can be played I want it to start automatically. Trying to do that with a timer, try to play and if that fails, try again.
The timer (timeout) doesn't seem to do that, however if I execute the script using a button, it does. What am I missing?
see the code below.
thanks,
Ron
PS: I commented out the setTimeout functions, to make the button work.
<!DOCTYPE html>
<html>
<body>
<script type="text/javascript">
function playVid() {
var videoElem = document.getElementById("IPcamerastream");
var playPromise = videoElem.play();
// In browsers that don’t yet support this functionality playPromise won’t be defined.
if (playPromise !== undefined) {
playPromise.then(function() {
// Automatic playback started!
videoElem.controls = true;
}).catch(function(error) {
// Automatic playback failed.
// setTimeout(playVid, 1000);
});
}
}
//setTimeout(playVid, 1000);
</script>
<button onclick="playVid()" type="button">Play Video</button><BR>
<video id="IPcamerastream" src="http://192.168.2.8:8080" width="960" height="540"></video>
</body>
</html>
Look into the features of the video html5 tag:(https://www.w3schools.com/tags/tag_video.asp)
one of the optional attributes is autoplay (Specifies that the video will start playing as soon as it is ready) so there is no need to set timeout.
<video id="IPcamerastream" src="http://192.168.2.8:8080" width="960" height="540" autoplay></video>
Move your script below the video element, and you should not need the timeout at all, because the element will already be initialized when the script is executed. So document.getElementById should resolve the element right away.
Using timers will introduce race conditions. If anything, you should add a listener to the DOMContentLoaded event.
Welcome Ron,
This is a well formatted question, but target browser info could also assist in helping you resolve your issue. Please consider adding it in future!
As for your problem, you tell us that you wish the video to autoplay, I'm assuming on page load?
I've also removed the duplicate source paste.
In this case, you only call playVid() from within the promise erroring out. The initial call is bound to the button click event.
In short, only clicking the button will initiate the playVid() function.
You need to add an event listener for DOM readiness and call playVid() within that. Otherwise, it's only being called when you click your button!
document.addEventListener('DOMContentLoaded', (event) => {
//the event occurred
playVid();
});
You can also use the autoplay HTML option in the video tag.
lang-html
<video {...} autoplay />
I had an almost similar problem ، when I received the video stream in a webrtc project, the video would not be displayed without clicking a button.
if you want to play automatically received stream video, you should add "muted" in the video tag.

Stop/skip video manually on click

I am trying to end a video when a user clicks a skip button allowing a user to skip the video.
I have been using the following, which is all good, and allows me to trigger an action when the video ends.
HTML
<video autoplay>
<source src="video.mp4" type="video/mp4">
</video>
<button class="skip">Skip video</button>
JS
$('video').on('ended',function(){....
//do stuff here
}
however, I want to do something like the following, but not sure how to implement this.
$('.skip').click(function(){
$('video').end();
});
The end() method in jQuery using to get back to the previous selector.
Description from jQuery docs :
End the most recent filtering operation in the current chain and return the set of matched elements to its previous state.
To stop the video get dom object and then use HTMLMediaElement#pause method. Although finally trigger the ended event programmatically using trigger() method.
$('.skip').click(function(){
$('video').trigger('ended')[0].pause();
});
$('.skip').click(function(){
$('video')[0].load();
$('video')[0].pause();
});
Moving a video to the end of playback is not a straightforward operation as you need to first get the length of the video, then move to that point.
A much simpler alternative is to extract the logic that's invoked under ended and call it when the button is clicked too. Try this:
$('video').on('ended', function() {
videoEnded();
})
$('.skip').click(function() {
$('video')[0].pause();
videoEnded();
});
function videoEnded() {
// do stuff here
}
Working example

Angularjs. How to get a global jquery object and other (jQuery plugin / Angularjs things!) ?

I want to use http://mediaelementjs.com/ to play multiple youtube video in my app. So i have created a directive who load mediaelementjs :
Directives
.directive('mediaelement', function() {
return {
link: function(scope, element, attrs) {
$(element).mediaelementplayer();
}
}
})
and i have my player like that :
Player
<video id="player" controls width="374px" height="301px" type="video/youtube" src="{{currentMusic}}" youtube>
Because it's a youtube video medialementjs use flash.
First Problem
currentMusic is initialized only later and when i change currentMusic the DOM is correctly modify but mediaelementjs don't change the source and when i click play nothing play because the source on the flash object is still {{currentMusic}} (not binded)
but if i put directly a youtube source in video src everything is ok.
and if i put a value in currentMusic when my app load and then i wait 2 sec to create mediaelementjs object everything is ok too !
Why ?
Second Problem
If i delete my first mediaelementjs object after a video have been play then i change currentMusic and then i create a new medialementjs object the source is correctly change.
so i have to use setSrc()
and so i would like to change source of the video when i click on a link in an other part of my app.
but how can i get my mediaelement object ?
So how make a list of youtube video play with angularjs and mediaelementjs ?
i really hope i'm clear... i spend two days trying to undestand thats happening and make things work but i fail...
Source :
How do I get mediaelement.js player state (paused, volume, etc.)?
http://johndyer.name/html5-video-wrapper-for-youtube-and-vimeo-api-mediaelement-js/
media player object from directive
put inside $observe your directive:
attrs.$observe('src', function(src) {
$(element).mediaelementplayer();
});
so you will init your plugin each time after SRC is changed
second issue has different solutions, for example using scope/service:
scope.mediaObject = $(element).mediaelementplayer();
UPD:
I've read docs for MediaElementPlayer, so should be pretty simple:
attrs.$observe('src', function(src) {
$(element).mediaelementplayer().setSrc(src);
});

Categories

Resources