IntersectionObserver not registering on iOS, but works on Android, in Nuxt - vue.js

What I have
I have made use of IntersectionObserver on my feed page. It behaves like TikTok. Scroll up or down to see the next video. On enter the video plays. On exit, it stops playing.
The Problem
It works on Android and Windows perfectly. On iOS, not so much. The videos are not autoplaying as expected. A bit strange though, is if I click the video (which calls play() on that video), and then scroll it out of view, the video does stop. When I scroll it back into view, it auto plays again. So I know that IntersectionObserver is being recognized, just not triggered initially.
The Code
==================================
The Feed Page, where the Observer is being instantiated:
data() {
return {
observer: null,
}
};
mounted() {
let config = { threshold: 0.1 };
if (process.client) {
if (/iPhone|iPad/i.test(navigator.userAgent)) {
config.root = null;
}
}
this.observer = new IntersectionObserver(function (entries) {
entries.forEach(({ target, isIntersecting }) => {
if (isIntersecting) {
target.play();
} else {
target.pause();
target.currentTime = 0;
}
});
}, config);
beforeDestroy() {
this.observer.disconnect();
},
HTML
<div
v-for="(post, index) in posts"
:key="index"
>
<MobileFeedItem :post="post" :observer="observer" />
</div>
=================================
The MobileFeedItem component
props: ["post", "observer"],
mounted() {
this.$nextTick(() => {
if (this.observer !== null && this.checkMediaTypeIsVideo(this.post)) {
this.observer.observe(
document.getElementById(
`video_${this.getContentId(this.post)}_mobile`
)
);
}
});
HTML
<video
:id="`video_${getContentId(post)}_mobile`"
:data-poster="getThumbnail(post)"
preload="none"
disablePictureInPicture
crossorigin
loop
playsinline
v-lazy-load
class="w-full h-full my-4"
>
<source :data-src="getMedia(post)" type="video/mp4" />
</video>
My Thoughts...
The observer is being instantiated and recognized, just not triggered. So is there a way that I can force the browser to wake up and become aware of the observer without having to click on each of the video elements first?

I found the answer. It wasn't anything wrong with the IntersectionObserver, but because my video was not muted.
"By default, autoplay executes only if the video doesn’t contain an audio track, or if the video element includes the muted attribute. Video playback pauses if the element gains an audio track, becomes unmuted without user interaction, or if the video is no longer onscreen"
source - https://developer.apple.com/documentation/webkit/delivering_video_content_for_safari
It also appears that we don't need intersection observer at all for Safari, as it does this for us. Huh.

Related

Can't change transition on the fly for a transition group

In my app, clicking a modal's close button makes it disappear with a fade animation whereas swiping it down makes it disappear with a swipe animation. This is done by changing the modal's <transition name> based on event.
The same thing doesn't seem to work with a transition group. Am I doing something wrong, or is it actually not possible?
CodeSandbox
Template:
<transition-group :name="itemTransition">
<div
v-for="item in items"
:key="item.id"
v-hammer:swipe.up="() => onSwipeUp(notification.id)"
>
</div>
</transition-group>
Script:
export default {
data () {
return {
applySwipeTransition: false
}
},
computed: {
itemTransition () {
return this.applySwipeTransition ? 'swipe' : 'fade'
}
},
methods: {
onSwipeUp (id) {
this.applySwipeTransition = true
this.$nextTick(() => {
this.closeItem(id)
this.applySwipeTransition = false
})
}
}
}
CSS:
.fade-leave-active {
animation: fade-out .75s;
}
.swipe-leave-active {
animation: slide-up .25s;
}
The problem lies in the timing of component update. You are switching the transition mode back to fade in the same update cycle as when the element is closed. Thus, when the next component update is triggered (by removal of the item), the transition is already switched back to fade. At this point, you may have guessed that all that needs to be done, is to switch the transition back in the next update, triggered by removal of the item:
onSwipeUp (id) {
this.applySwipeTransition = true
this.$nextTick(() => {
this.closeItem(id)
this.$nextTick(()=>{
this.applySwipeTransition = false
})
})
}
Since there are no reasons to wait for component update to close the item, you can simplify the code a bit:
onSwipeUp (id) {
this.applySwipeTransition = true
this.closeItem(id)
this.$nextTick(() => {
this.applySwipeTransition = false
})
}
Here is your working sandbox: https://codesandbox.io/s/vue-template-forked-60lkk?file=/src/App.vue
So, I've worked around with your CSS by manually changing the name of the <transition-group to either fade or swipe to see if the there's a problem with the CSS animations.
Verdict: The fade works. swipe only transitions the list-item off the page by a click and drag, not true swipe, if that concerns you (by the way, my swipe is MacOS swipe - two-finger, no click)
Still, without changing the CodePen, the issue seems to be with your computed property where there's nothing telling the name to change dynamically even though you've bound it to a computed property - the logic for itemTransition() seems to always default to fade because the applySwipeTransition would never equal to "swipe", given that the CSS does work when you manually change name to swipe (see "Verdict)".
To see where the underlying issue was, I worked around with your itemTransition():
computed: {
itemTransition() {
return this.applySwipeTransition ? "fade" : "swipe";
},
Switching the order of the fade and swipe now makes swipe work. I hope this gives you some insight into the issue. You may need to create a custom Vue directive or event to handle the swipe / fade logic if needed.

Webpack hot reload doesn't stop html5 audio

In a Vue app I play background music, using the audio tag :
<audio id="music" autoplay loop>
<source
:src="musicfilename"
type="audio/mp3"
/>
</audio>
This works fine, but when the app is restarted during development, by Webpack 'hot reload', the music that was playing isn't stopped, so it keeps playing multiple music instances at the same time!
When I check with document.getElementsByTagName there is really only 1 instance of AUDIO, so I can't find a way to stop the previous audio instances from playing.
Any idea how to solve this problem?
You should track when your module is being hot replaced, and when it is just stop the audio player.
<audio id="music" ref="player" autoplay loop>
<source
:src="musicfilename"
type="audio/mp3"
/>
</audio>
export default {
...
mounted() {
if (module.hot) {
module.hot.dispose(this.hmrHandler);
}
},
destroyed() {
if (module.hot) {
module.hot.removeDisposeHandler(this.hmrHandler)
}
},
methods: {
hmrHandler() {
const { player } = this.$refs
player.pause()
// Lines below can also help
// player.currentTime = 0
// player.src = ''
},
},
...
}
Update:
Howler.unload() placed in the mounted hook solved the problem.
Webpack HMR API.
Look at dispose and removeDisposeHandler.

admob with nativescript-vue integration

I'm trying to add admob to my app with "nativescript-admob", here is the code i have for now :
<template>
<Page class="page">
<StackLayout class="hello-world">
<Label text="my home page"/>
</StackLayout>
</Page>
</template>
<script>
const admob = require("nativescript-admob");
export default {
mounted() {
admob.createBanner({
// if this 'view' property is not set, the banner is overlayed on the current top most view
// view: ..,
testing: true, // set to false to get real banners
size: admob.AD_SIZE.SMART_BANNER, // anything in admob.AD_SIZE, like admob.AD_SIZE.SMART_BANNER
androidBannerId: "ca-app-pub-AAAAAAAAAAA/AAAAAAAAA", // add your own
margins: {
// if both are set, top wins
//top: 10
bottom: 50
},
}).then(
function() {
console.log("admob createBanner done");
},
function(error) {
console.log("admob createBanner error: " + error);
}
)
},
}
</script>
i try to launch the admob on "mounted" but i cant make it work, does anyone integrated admob this way with nativescript-vue ? in my case i don't even see the log "admob createBanner done" log so maybe i don't use this plugin well.
the admob.createBanner() function has to be surrounded by a setTimeout() to launch after, it seems it has to be launch when a page is fully loaded. thx for the help of the slack nativescript-vue channel contributors !

Aframe Add Preloader to image loading in sky

I have a scene where I am changing the src for sky using buttons I created "outside the scene". Currently everything works fine but I would like to show a preloader while waiting for the next image to load.
Here you can see my scene: http://scriptstrainer.com/vr_training/
Below I have provided some of my code:
<a-scene>
<a-sky src="images/0-1.jpg" id="img-src">
</a-scene>
<div>
<img src="images/t1.png">
</div>
<div>
<img src="images/t2.png">
</div>
<div>
<img src="images/t3.png">
</div>
<script>
var sky = document.querySelector('#img-src');
var button1 = document.querySelector('#button1');
var button2 = document.querySelector('#button2');
var button3 = document.querySelector('#button3');
button1.addEventListener('click', function() {
sky.setAttribute('src', 'images/0-1.jpg');
});
button2.addEventListener('click', function() {
sky.setAttribute('src', 'images/2.JPG');
});
button3.addEventListener('click', function() {
sky.setAttribute('src', 'images/3.JPG');
});
</script>
Thanks for your assistance...
https://aframe.io/docs/0.4.0/components/material.html#events_materialtextureloaded
There's an event materialtextureloaded you can use to detect when the texture has loaded onto the mesh. In between the time you request to set the texture and the time the texture is set, you can display a loading graphic.
button1.addEventListener('click', function() {
sky.setAttribute('src', 'images/0-1.jpg');
// Display something in the meantime.
sky.addEventListener('materialtextureloaded', function () {
// Small timeout just in case?
setTimeout(function () { // Remove the placeholder. }, 100);
});
});
The loading graphic can be like a spinning object in the scene, a fade-in black mask around the camera (as used in https://github.com/aframevr/360-image-gallery-boilerplate). It depends on what you want it to be.
I had the same scenario where I wanted to add a preloader and only when the image is displayed, to remove the preloader.
I tried using events 'load' and 'loaded' but didn't work as I found out images are not displayed once they finish loading.
Eventually I got help from the AFrame GitHub page and that's how I did it:
<a-assets>
<img id='img-src' src='image.jpg'/>
</a-assets>
<a-sky src='#img-src' id='sky-id'></a-sky>
<script type='text/javascript'>
var skyEl = document.querySelector('#sky-id');
function loaded()
{
var preloader = document.querySelector('#preloader');
preloader.style.display = "none";
}
skyEl.addEventListener('materialtextureloaded', loaded);
</script>

How to check if SOUND is ON/OFF on HTML5 video

I need to show Sound icon on top of the video, when sound is OFF and hide it when sound is ON. For some reason the code below is not working.
if (video.prop('muted') === true) {
video.mouseenter( function() {sound.show()} ).mouseleave( function() {sound.hide()} );
}
else {
sound.hide();
}
<video id="video" controls muted preload="none" width="446" height="250"></video>
I figured it out. Now it works like that.
video.mouseenter( function() {
if (video.prop('muted') === true) {
sound.show()
}
else {
sound.hide()
}
});
video.mouseleave( function() {
sound.hide();
});
Considering your video element:
<video id="video" controls muted preload="none" width="446" height="250">
</video>
You can determine whether sound is on by testing the volume and muted media properties of the element:
var video = document.getElementById("video");
if (video.muted || video.volume === 0) {
// Sound is off
}