Angular YouTube Control and JavaScript Events - javascript

I am experiencing performance issues using the Angular YouTube control. When using the Safari Timeline monitoring, I notice nearly continuous JavaScript events being fired. My CPU time is high and performance is low. When I comment out the YouTube control, the Timeline shows radically lower events, CPU usages drops significantly, and overall performance is acceptable.
So, there seems to be something unexpected occurring when using that YouTube control. I have been researching for sometime and cannot find any article that speaks to what I am encountering.
I am hoping someone will have some insight.
I have included copies of code and screen prints of the Safari Timeline.
The HTML for the control...
<youtube-player
[videoId]="videoId"
suggestedQuality="large"
[height]="156"
[width]="275">
</youtube-player>
The TypeScript...
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'app-youtube-player',
templateUrl: './youtube-player.component.html',
styleUrls: ['./youtube-player.component.css']
})
export class YoutubePlayerComponent implements OnInit {
private apiLoaded = false;
#Input() videoId: string;
constructor() { }
ngOnInit(): void {
if (!this.apiLoaded) {
const tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';
document.body.appendChild(tag);
this.apiLoaded = true;
}
}
}
The screen print of the Safari Timeline with the YouTube control rendered.
The screen print of the Safari Timeline with the YouTube control commented out.
I have tried this with multiple versions of the Angular YouTube library. I am currently using the most recent version - 15.1.4.
Any ideas?

Related

Switch the video file for the HTML5 video tag in run-time smoothly

I'm working on an Angular [4+] application.
In one of my HTML templates I have a HTML5 video tag.
I have n files located on the disk:
1.mp4
2.mp4
...
n-1.mp4
n.mp4
Once the user pressed the Play button, I want the video to start playing the first file:
1.mp4
Once the file ends, I want the same HTML video tag to start playing the file:
2.mp4
and so on till it reaches the n.mp4 and play it to it's end.
I want that the switching of the files would be as smooth as possible, with no latency, no loading time, as if the HTML video tag plays a single file without switching n files.
The only solution I have in mind is changing the src attribute of the HTML video tag, and maybe implement some buffer so that if the x.mp4 is about to reach it's end, the x+1 file already starts loading. Any ideas on how to implement this?
What do you think is the ideal solution for this scenario?
(Note that combining those n files into one in advance is not an option, cause those files are created dynamically, and I know the exact amount of files only once the user has pressed play on the HTML video tag).
You can try to use ended event on video
Note: Not tested, it could be in the comment but I think it would be too long for the comment
In your template add following code
<video #video width="256" height="192" id="myVideo" controls autoplay>
<source #source src="{{count}}.mp4" id="mp4Source" type="video/mp4">
Your browser does not support the video tag.
</video>
import ViewChild, ElementRef, Renderer2 from '#angular/core' in your component and in ngAfterViewInit lifecycle hook
count = 1;
#ViewChild('video') video: ElementRef;
#ViewChild('source') source: ElementRef;
constructor(private rd: Renderer2){}
ngAfterViewInit() {
let player = this.video.nativeElement;
let mp4Vid = this.source.nativeElement;
this.rd.listen(player, 'ended', (event) => {
if(!event) {
event = window.event;
}
this.count++;
this.rd.setAttribute(mp4Vid, 'src', `${this.count}.mp4`)
player.load();
player.play();
})
}
Hope this might help you and for further reference ended event for video
You can try to use the [hidden] attribute on the videos, you can check if it buffers the video.
Once one video is done you can set the video to hidden = true and the next video to hidden = false.
And use the autoplay = true in order to play it without having the user click on play.

Javascript audio not working properly with React and Socket.io on iOS

I'm working on creating a simple web app that uses React and Socket.io. The principle is that there are two devices connected, which send each other nothing but 'go to this view' -type messages, which then react renders just fine.
The issue I have is with playing audio clips with javascript when the views change. When I click on the button on the iPad that changes the view, the audio starts to play just fine, but when the view change is initiated by another device over websocket connection, the audio won't play.
All this works just fine on desktop both Chrome and Safari. This doesn't work on iPad running iOS 9.3.5 nor iPhone running iOs 11.0.1 (tested both safari and chrome)
Here's the code for playing audio file:
// Importing audio files
import responsePromptSound from 'A/responsePrompt.aac';
import endsound from 'A/endsound.aac';
function playSound(view) {
// Stop playing a repeating sound if there is one
if (window.soundPlaying) {
clearInterval(window.soundPlaying);
}
// The audio clips to be used in each view
const views = {
start: null,
waitingForResponse: null,
responsePrompt: responsePromptSound,
end: endsound,
};
// Current audio clip
const soundClip = views[view];
if (!soundClip) {
return;
}
const audio = new Audio(soundClip);
function playMe() {
audio.play();
}
// Repeating notification sound
if (view === 'responsePrompt') {
window.soundPlaying = setInterval(() => {
playMe();
}, 7 * 1000);
}
// Play always at least once
playMe();
}
export default playSound;
Here's the code that renders a button that also calls the playSound:
import React from 'react';
import PropTypes from 'prop-types';
import playSound from 'C/Audio.js';
function Button(props) {
// text content for each view
const views = {
start: ['start1', 'start2'],
waitingForResponse: null,
responsePrompt: ['prompt1', 'prompt2'],
end: ['end1', 'end2'],
};
// Current button text
const btnText = views[props.view];
let btn;
// Different views for button
if (!btnText) {
btn = null;
} else if (props.view === 'end') {
btn = (
<div className="end-box">
<div className="end-box-inner">
<div className="button-txt-one">{btnText[0]}</div>
<div className="line" />
<div className="button-txt-two">{btnText[1]}</div>
</div>
</div>
);
} else {
btn = (
<button onClick={props.changeView}>
<div className="button-inner">
<span>
<div className="button-txt-one">{btnText[0]}</div>
<div className="line" />
<div className="button-txt-two">{btnText[1]}</div>
</span>
</div>
</button>
);
}
playSound(props.view);
return (
btn
);
}
Button.propTypes = {
view: PropTypes.string.isRequired,
changeView: PropTypes.func.isRequired,
};
export default Button;
The socket connection only triggers setState() on the parent element with the correct view.
EDIT: Forgot to add that I also tried using MutationObserver on the main element with everything turned on but still got nothing.
Ps. I know there's like a billion things not done all that well here, bear with me.
I found this article which describes the limitations of HTML5 audio on iOS. Related to this issue, on iOS the playing of the audio must always be initiated by user action, and the only solution is to design the app in such a way that the user always initiates the playback.
I'm gonna look into some possible workarounds and will edit my answer asap to include my findings, even though at least according to the article there's no workaround. It's from 2012 though, so there might be some changes.

Ionic 2 range slider causing "Expression value changed after it was checked" error

I am building an audio player in ionic 2 and using the javascript Audio() object. but when i attach an ionic 2 range slider to the audio player to show progress it doe not shift except another action is carried out by the user on the app. And when different actions ae being carried out in the app it throws this exception
Subscriber.js:229 Uncaught EXCEPTION: Error in ./player class player - inline template:9:105
ORIGINAL EXCEPTION: Expression has changed after it was checked. Previous value: '3.17455955686814'. Current value: '3.1862762409060017'
#Component(
template:`
<div>
<ion-range min='0' max='100' style="margin-top;0px;padding-top:0px;padding-bottom:0px;" [(ngModel)]="audio.currentTime*100/audio.duration" danger ></ion-range>
</div>
`
)
export class player{
audio:any;
constructor(){
this.audio=new Audio();
this.audio.src='songs/bing.mp3';
this.audio.play();
}
}
Any ideas what could be the problem?
Angular 2 uses Zone.js to power its default change detection method, which monkey-patches async browser functions like setTimeout and setInterval so that it can refresh the change detection tree whenever an update occurs.
It won't, however, be able to figure out about something like currentTime changing on an audio element. In debug mode, Angular is smart enough to know that the value for that binding has changed since it last checked, and throws an error (because it means you have side-effecty code and that won't refresh properly on changes). This error will go away if production mode is enabled, but ideally you shouldn't need to resort to that.
Instead, use the timeupdate event of the audio element tag and only refresh the currentTime binding when it occurs. Zone.js can handle event listeners, so Angular won't complain.
#Component(
template:`
<div>
<ion-range min='0' max='100' style="margin-top;0px;padding-top:0px;padding-bottom:0px;" [(ngModel)]="currentTime*100/audio.duration" danger ></ion-range>
</div>
`
)
export class player{
audio:any;
currentTime: number;
constructor(){
this.audio=new Audio();
this.audio.src='songs/bing.mp3';
this.audio.play();
this.audio.addEventListener('timeupdate', () => {
this.currentTime = this.audio.currentTime;
});
}
}

Does anybody know how to detect orientation change for Nativescript?

I have created two xml files for a screen. one named "login-page.port.xml" and the other one is "login-page.land.xaml".
Is there a way to programmatically detect orientation change within the application?
Thanks,
Keks
Yes, there is a way to detect orientation change in the application. The easiest method is in the page you want to get the orientation; add the loaded and unloaded events to your page.xml files:
<Page loaded="pageLoaded" unloaded="pageUnloaded">
In your page.js file; add code like this:
var application=require('application');
exports.pageLoaded = function() {
application.on(application.orientationChangedEvent, setOrientation);
};
exports.pageUnloaded = function() {
application.off(application.orientationChangedEvent, setOrientation);
}
function setOrientation(args) {
// Will console out the new Orientation
console.log(args.newValue);
}
A couple notes;
1. You want to add & remove the event listener as you enter and exit the page; otherwise the listener is global and will continue to fire when the orientation changes even when you are on another page.
2. You can add multiple listeners to this event, so again if you don't remove the listener, then each time you re-load this page you will add ANOTHER copy of this listener to the group and so it will fire as many times as you have added it.
3. On android in v1.6.1 their is a bug in the orientation firing. https://github.com/NativeScript/NativeScript/issues/1656 (Basically if you start in Landscape, rotate it won't detect it. The issue has the patch that I've applied manually to my code).
Now Looking at your actual example; are you aware that at least as of version 1.6.x of NativeScript it will only load the XML for which ever orientation it currently is set as; but it will NOT load the other orientation's xml when you rotate. So, if you are in Landscape; enter the screen then rotate it will still be using the Landscape xml file.
Now with all that said you might seriously consider looking at the nativescript-orientation plugin which I am the author of, this plugin simplifies dealing with screen orientation and allows you to only have ONE .xml file and then change things via css.
For those using angular2-nativescript typescript
Please refer to the on method found in the documentation
on(event: "orientationChanged", callback: function, thisArg?: any):
any Defined in application/application.d.ts:232 This event is raised
the orientation of the current device has changed.
Parameters
event: "orientationChanged"
callback: function
(args: OrientationChangedEventData): void Parameters
args: OrientationChangedEventData
Returns void Optional thisArg: any
Returns any
Example in use:
#Component({
selector: "Test"
//templateUrl, styleUrls, etc...
})
export class TestComponent{
constructor(){
on("orientationChanged", this.onOrientationChanged)
}
public onOrientationChanged = (evt) => {
console.log("Orientation has changed !");
console.log(evt.eventName); // orientationChanged
console.log(evt.newValue); //landscape or portrait
};
}

Ember updating video sources but not being reflected by browser

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}}

Categories

Resources