Audio being skipped during fast ontouch IOS - javascript

I have created a flip tile memory game. A sequence of tiles will flip over displaying a colour. The user must remember the sequence and repeat it. When the user selects correctly a correct Mp 3 is played. On the Iphone if the tiles are selected quickly the audio isn't being played for each touch, its as though the audio is being skipped for some. link
const elements = {
gameContainer: $('#game-container'),
gameMenu: $('#game-menu'),
audioPlayer: document.querySelector('#player'),
audioPlayer2: document.querySelector('#player2'),
audioPlayer3: document.querySelector('#player3'),
tiles: $('.tile'),
correctAlert: $('#correct-alert'),
wrongAlert: $('#wrong-alert'),
failAlert: $('#fail-alert'),
alertModal: $('#alert-modal'),
stageNumber: $('.stage-number'),
maxStageNumber: $('.max-stage-number'),
gamemodeCheckbox: $('#gamemode-checkbox'),
stageProgress: $('#stage-progress'),
waitText: $('#wait-text'),
wonAlert: $('#won'),
goText: $('#go-text')
};
function tileClicked(tile) {
console.dir(tile)
// only allow clicking on tiles when game is started and game is not showing pattern
if (!game.showing && game.started && !tile.classList.contains('flip-card-onclick')) {
flipTile(tile);
// check if game reached maximum number of stages i.e. game has been won
if (game.playerMove <= game.maxStageNumber) {
// check if current move (tile clicked) matches the tile in the generated pattern
if (parseInt(tile.id) == game.currentGame[game.playerMove]) {
// increase the pattern pointer
game.playerMove++;
// play sound when correct tile has been clicked
elements.audioPlayer.pause();
elements.audioPlayer.currentTime = 0;
elements.audioPlayer.play();
// check if we reached the end of the current pattern
if (game.playerMove == game.currentGame.length) {
// update the progress bar
elements.stageProgress.css('width', `${(game.currentGame.length / game.maxStageNumber) * 100}%`);
// show alert prompting user to go to the next stage
elements.correctAlert.modal('show');
}
// current move did not match current pattern, wrong move
} else {
if (game.strictGamemode) {
elements.audioPlayer2.play();
// show fail alert and prompt to restart or exit game if strict mode has been selected
elements.failAlert.modal('show');
} else {
// show wrong move alert and prompt to show pattern again
elements.audioPlayer2.play();
elements.wrongAlert.modal('show');
}
}
}
}
}
<!--Audio Player-->
<audio controls id="player" class="d-none">
<source id="player-src" src="assets/audio/correct.mp3">
</audio>
<audio controls id="player2" class="d-none">
<source id="player-src-2" src="assets/audio/incorrect.mp3">
</audio>
<audio controls id="player3" class="d-none">
<source id ="player-src-3" src="assets/audio/won.mp3">
</audio>

It is very hard to tell where your bug comes from, so your solution may not be so easy to find. Some research might tell you some things but otherwise there is an alternative you could try.
The Web Audio API is an interface where you can get more control over the audio you play. So in your case instead of manipulating the <audio> element, use the Web Audio API to play your audio files.
Down here I've created a snippet which utilizes this API. It currently selects all of your <audio> elements and extracts the sound into a node which then can use to play the sound. This gives you control over how the sound plays.
So here it creates an object, which is stored in the sounds constant, that holds all the names as keys and the players as values. Something like this:
const sounds {
'correct': MediaElementAudioSourceNode,
'incorrect': MediaElementAudioSourceNode,
'won': MediaElementAudioSourceNode
};
Each of those MediaElementAudioSourceNode are the sounds which can be played. Later in the script there is a playSound function, which plays one of the sounds found in your sounds object.
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioContext = new AudioContext();
const audioElements = document.querySelectorAll('audio');
const createAudioSources = audioElements => {
const audioSources = {};
for (const audioElement of audioElements) {
const name = audioElement.dataset.name;
const track = audioContext.createMediaElementSource(audioElement);
audioSources[name] = track;
}
return audioSources;
};
const sounds = createAudioSources(audioElements);
function playSound(track) {
const sound = sounds[track];
if (sound === undefined) return;
sound.connect(audioContext.destination);
sound.start(audioContext.currentTime);
};
playSound('correct');
playSound('incorrect');
playSound('won');
So all of this could be added above your original script so that the sound files are loaded and ready for use. And then use the playSound() function anywhere in your script whenever you want to play anyone of the sounds. Example below:
function tileClicked(tile) {
console.dir(tile)
// only allow clicking on tiles when game is started and game is not showing pattern
if (!game.showing && game.started && !tile.classList.contains('flip-card-onclick')) {
flipTile(tile);
// check if game reached maximum number of stages i.e. game has been won
if (game.playerMove <= game.maxStageNumber) {
// check if current move (tile clicked) matches the tile in the generated pattern
if (parseInt(tile.id) == game.currentGame[game.playerMove]) {
// increase the pattern pointer
game.playerMove++;
// play sound when correct tile has been clicked
playSound('correct');
// check if we reached the end of the current pattern
if (game.playerMove == game.currentGame.length) {
// update the progress bar
elements.stageProgress.css('width', `${(game.currentGame.length / game.maxStageNumber) * 100}%`);
// show alert prompting user to go to the next stage
elements.correctAlert.modal('show');
}
// current move did not match current pattern, wrong move
} else {
if (game.strictGamemode) {
playSound('incorrect');
// show fail alert and prompt to restart or exit game if strict mode has been selected
elements.failAlert.modal('show');
} else {
// show wrong move alert and prompt to show pattern again
playSound('incorrect');
elements.wrongAlert.modal('show');
}
}
}
}
}
Also, add a data-name attribute to each <audio> element so that JavaScript knows how to call each player and it's accompanying sound.
<audio controls id="player" class="d-none" data-name="correct">
<source id="player-src" src="assets/audio/correct.mp3">
</audio>
<audio controls id="player2" class="d-none" data-name="incorrect">
<source id="player-src-2" src="assets/audio/incorrect.mp3">
</audio>
<audio controls id="player3" class="d-none" data-name="won">
<source id ="player-src-3" src="assets/audio/won.mp3">
</audio>
All my code above is untested and might throw an error, or worse, makes no difference at all. But hey, at least it's worth a shot.

Related

Mute and unmute music once it's started playing in Adobe Animate

I'm trying to create a game that demonstrates the different instruments in a band. I've managed it in AS3, but I'm struggling in Javascript.
You can either listen to all the instruments playing a piece of music, or click on an instrument to enable or disable it, thus bringing it in or taking it out of the mix.
I have 8 tracks that play simultaneously when the play button (play_btn) is pressed. But whether each instument plays or is muted depends on whether another button (e.g. congas_btn) is pressed. If it isn't pressed, the conga player, for example, (congaPlayer_mc) is greyed out and the conga audio track is muted. If it is pressed, the conga player has full alpha and is unmuted.
Each instrument should be able to be muted or unmuted at will whilst the track is playing.
What actually happens is that whichever state I choose (muted or unmuted) before I click play, determines whether the track plays muted or unmuted. But once the track has started, the congas_btn doesn't have any more effect on the volume. It just toggles its alpha value.
How do I get it to carry on affecting the volume for as long as the track is playing?
Thanks.
This is my code:
let root = this
let congasActive = false
let audioPlaying = false
this.congaPlayer_mc.alpha = 0.5
this.play_btn.addEventListener("click", playAudio.bind(this))
function playAudio() {
const playCongas = createjs.Sound.play("congas")
if(!audioPlaying){
audioPlaying = true
if(congasActive){
playCongas.volume = 1
} else {
playCongas.volume = 0
}
} else {
audioPlaying = false
const stopCongas = createjs.Sound.stop("congas")
}
}
this.congas_btn.addEventListener("click", activateCongas.bind(this))
function activateCongas() {
if(!congasActive){
congasActive = true
root.congaPlayer_mc.alpha = 1
} else {
congasActive = false
root.congaPlayer_mc.alpha = 0.5
}
}

jQuery click Handler disables play button functionality in audio element

EDIT #1 with connexo’s solution
Within the following HTML, I want to click in the parent 'li' to play the child 'audio'
The problem is noted in the alert calls within the JS; i.e., trying to compare a HTMLLIElement with a HTMLAudioElement.
How can I successfully do that?
HTML:
I have several:
<li>
text here<br>
<audio controls preload="auto">
<source src="aSong.mp3">
</audio>
</li>
JS:
$('li:has(audio)').on("click", function(evt) {
var m, theSongInArray,
// thisSong = $(this)[0]; // HTMLLIElement
// now a HTMLAudioElement thanks to connexo
$thisLI = $(this);
thisSong = $thisLI.find('audio')[0];
// itsAllSongs defined elsewhere
for (m=0; m < itsAllSongs.length; m++)
{
// HTMLAudioElement
theSongInArray = itsAllSongs[m];
if (thisSong == theSongInArray)
{
if ( thisSong.paused )
thisSong.play();
else
{
thisSong.pause();
thisSong.currentTime = 0;
}
}
}
});
Note: connexo's solution is perfect - but now I have a new problem =
My intent above, which connexo has solved, was to be able to click anywhere within 'li:has(audio)' and have the toggling effect between .play() and .pause() .. and it now does that.
BUT, now the anywhere within 'li:has(audio)' seems to not include the play icon of:
If I click on the slider, the progress bar, the time-stamp, or anywhere else on the long <audio> control it works great, but not on the play icon on the far left of the above control.
SUPER wierd - this 2nd challenge goes away (I can then click just on the play icon) if I remove:
$('li:has(audio)').on("click", function(evt) {
})
Hopefully, connexo has another brilliant solution.
EDIT #2 = solution of the current problem:
Now, I can click on either the li area surrounding the <audio> element, or on just the <audio> element itself. As a matter of interest, when I click on just the play icon of <audio>, the song will play and, if I click on the position slider, the slider will move without playing/pausing the song itself:
/*
alert(evt.target); // = HTMLLIElement, or HTMLAudioElement
*/
if (evt.target.id.length == 0)
thisSong = $(this).find('audio')[0]; // HTMLLIElement -> HTMLAudioElement
else // >= 3, e.g., = "SSB" or "AIRFORCE"
thisSong = $(this)[0]; // [object HTMLAudioElement]

Can't make nativescript-videoplayer and nativescript-audioplayer work alongside each other?

So i'm currently building an app using the multiplatform framework of nativescript. I've got the two plugins in question to work by themselves, but when both of them runs at the same time, and i wish to pause both tracks (video and audio with separate track files), only one of them stops, while the other continues. Why is that?
To start off, my nativescript audioplayer code is:
var videoViewModel = new Observable();
videoViewModel.audioPlayer = new TNSPlayer();
let audioParams = {
audioFile: selectedVideoEntity.audioPath_,
loop: true,
completeCallback: function(){},
errorCallback: function(error){console.log("failed with: ", error)}};
let audioPlayerCreatedCallback = () => {
videoViewModel.audioPlayer.getAudioTrackDuration().then((duration) => {
// iOS: duration is in seconds
// Android: duration is in milliseconds
console.log(`song duration:`, duration);
});
}
//todo: change to something more meaningfull (new RegExp('^' + "https\:\/\/|http\:\/\/", 'i')).test(selectedVideoEntity.audioPath)
if(selectedVideoEntity.audioPath.startsWith("http")){
console.log("getting from url");
videoViewModel.audioPlayer.playFromUrl(audioParams).then(audioPlayerCreatedCallback);
}
else{
videoViewModel.audioPlayer.playFromFile(audioParams).then(audioPlayerCreatedCallback);
}
When this is setup, I move on to making the videoplayer. This is implemented in xml.
<Page xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:VideoPlayer="nativescript-videoplayer"
loaded="loaded" class="page" navigatingFrom="onNavigatingFrom" actionBarHidden="true">
<StackLayout id="layout">
<VideoPlayer:Video id="videoPlayer" controls="true" loop="false"
finished="{{ videoFinished }} "
src="{{ videoPlayerViewModel.videoPath }}" observeCurrentTime="true" muted="true" />
<VideoPlayer:Video id="rewardVideoPlayer" controls="true" loop="false"
finished="{{ rewardVideoFinished }} "/>
</StackLayout>
</Page>
And then I grab the object from the associated page file
let videoPlayer = page.getViewById("videoPlayer");
Expect the video to have it's source and all set at this point.
Now is where the issues begin. The thing i want is to pause the video and the audio at the same time, so my approach is to pause the audio, whenever i call the cycleVideoPlayers pause function and afterwords, like this:
videoViewModel.audioPlayer.pause();
videoViewModel.videoPlayer.pause();
At this point only one of them stops (mostly the audio) and the video continues on. Any ideas why?
Links to the plugins:
https://github.com/bradmartin/nativescript-audio
https://github.com/bradmartin/nativescript-videoplayer
TO CLARIFY THE SITUATION
This only pauses one of the tracks and resumes the paused track, without having any effect
videoViewModel.audioPlayer.pause();
videoViewModel.videoPlayer.pause();
// Wait a few seconds
videoViewModel.audioPlayer.resume();
videoViewModel.videoPlayer.play();
This pauses the video and resumes flawlessly
videoViewModel.videoPlayer.pause();
// Wait a few seconds
videoViewModel.videoPlayer.play();
Same goes for audio
videoViewModel.audioPlayer.pause();
// Wait a few seconds
videoViewModel.audioPlayer.resume();
The explicit code that tries to pause and resume is given here. This code is in an exports.loaded function, in one of my nativescript page .js files.
cycleVideoPlayer.on(gestures.GestureTypes.swipe, function(eventdata) {
console.log("Swipe detected, with direction: " + eventdata.direction);
if (!paused) {
(function() {
vm.audioPlayer.pause();
vm.cycleVideoPlayerViewModel.pause(); //This doesnt work alongside audiopause.. why?
paused = true;
console.log("Paused");
});
} else {
(function() {
vm.audioPlayer.resume();
vm.cycleVideoPlayerViewModel.play();
paused = false;
console.log("Resumed");
});
}
});
If anybody have an idea or need clarification on the matter, please comment :)

Is it possible to embed actions in html5 video?

Is there any way to play a video in html5, stop and execute javascript at known points within the video?
Yes, try this. You have to use the currentTime property. Start with that and start your javascript functions from there. Is that what you're looking for?
var vid = document.getElementById("video");
//Add a timeupdate listener
video.addEventListener("timeupdate", function(){
//Check for video properties from within the function
if (this.currentTime == 5)
this.pause();
//cal javascript
}
}
Looking at the accepted answer over here it looks like it is possible.
To pause the video you just do this:
var mediaElement = document.getElementById("video"); // create a reference to your HTML5 video
mediaElement.pause(); // pauses the video
If you want to play the video, do this:
mediaElement.play(); // plays the video
To get the current time in the video, do this:
mediaElement.currentTime; // gets the current time
Here's an example linking them all up:
var mediaElement = document.getElementById("video");
if(mediaElement.currentTime == 35){
mediaElement.pause();
// do whatever else you would like
mediaElement.play();
}
The MDL documentation is here, there are plenty of other properties you might find helpful.
Hope this helps!
Yes, by doing like this you can.
Note that you have to look for a time using bigger than > bigger than (as the chance to match an exact millisecond is almost zero), and have a variable in one way or the other to know which ones is done.
window.addEventListener('load', function() {
var cur = document.querySelector('#cur'),
vid = document.querySelector('#vid')
})
var appDone = {"done7":false,"done4":false}
vid.addEventListener('timeupdate', function(e) {
if (e.target.currentTime > 7 && !appDone.done7) {
appDone.done7 = true;
e.target.pause();
//do something
cur.textContent += ", " + "done7 once";
e.target.play();
}
if (e.target.currentTime > 4 && !appDone.done4) {
appDone.done4 = true;
e.target.pause();
//do something
cur.textContent += ", " + "done4 once";
e.target.play();
}
})
<video id="vid" width="320" height="176" controls>
<source src="http://www.w3schools.com/tags/mov_bbb.mp4" type="video/mp4">
<source src="http://www.w3schools.com/tags/mov_bbb.ogg" type="video/ogg">
Your browser does not support HTML5 video.
</video>
<p id="cur"></p>

Request Full Screen HTML5 Video onPlay

I'm using the following code to trigger fullscreen when a user clicks on the play button on a <video> element:
var video = $("#video");
video.on('play', function(e){
if (video.requestFullscreen) {
video.requestFullscreen();
} else if (video.mozRequestFullScreen) {
video.mozRequestFullScreen();
} else if (video.webkitRequestFullscreen) {
video.webkitRequestFullscreen();
}
});
But nothing happens when I click the play button.
Any idea's why?
EDIT: Here's my HTML code:
<video width="458" height="258" controls id='video' >
<source src='<?= bloginfo('template_directory'); ?>/inc/pilot.mp4' type="video/mp4">
<source src='<?= bloginfo('template_directory'); ?>/inc/pilot.ogv' type="video/ogg">
<source src='<?= bloginfo('template_directory'); ?>/inc/pilot.webm' type="video/webm">
</video>
There are a couple things going on here:
First, in your code, video is a jQuery object, not the actual video element. For a jQuery object, you can reference it like this:
var actualVideo = video[0]; // (assuming '#video' actually exists)
Second, for security and good user experience, browsers will only let you trigger full screen inside a user-triggered event, like a 'click'. You can't have every web page going to full screen as soon as you visit it, and you can cause a video to start playing automatically, which would violate that rule.
So an alternative solution would be to request fullscreen in a click event, like this:
var video = $("#video");
video.on('click', function(e){
var vid = video[0];
vid.play();
if (vid.requestFullscreen) {
vid.requestFullscreen();
} else if (vid.mozRequestFullScreen) {
vid.mozRequestFullScreen();
} else if (vid.webkitRequestFullscreen) {
vid.webkitRequestFullscreen();
}
});
Ideally, you'd probably want to build out a more complete player ui, but this should give you the general idea.
A less verbose way to toggle full screen combining answers from this and other questions.
This should handle all browser flavours: chromium- and webkit-based, firefox, opera, and MS-based browsers.
var p = document.querySelector('#videoplayer');
if (!window.isFs) {
window.isFs = true;
var fn_enter = p.requestFullscreen || p.webkitRequestFullscreen || p.mozRequestFullScreen || p.oRequestFullscreen || p.msRequestFullscreen;
fn_enter.call(p);
} else {
window.isFs = false;
var fn_exit = p.exitFullScreen || p.webkitExitFullScreen || p.mozExitFullScreen || p.oExitFullScreen || p.msExitFullScreen;
fn_exit.call(p);
}
p represents the DOM object of the video element, and window.isFs is just a random variable for storing the current fullscreen state.
If your player is a jQuery object then you can get the underlying DOM-element with var p = player.get(0).

Categories