HTML5 Video - currentTime not setting properly on iPhone - iphone

I have a basic HTML5 video set up from which I load one of four videos. The problem I'm having is that when I load the next video, it continues playing from the previous time position. Efforts to set the currentTime property seem to be either short lived or ignored entirely.
I have added listeners to a collection of events and have something like this in each one;
myPlayer.addEventListener("loadeddata", function() {
console.log(" loadeddata: before = " + myPlayer.currentTime);
myPlayer.currentTime = 0.1;
console.log(" loadeddata: after = " + myPlayer.currentTime);
}, false);
Sometimes I see the time change for one event but not persist correctly;
durationchange: before = 19.773332595825195
durationchange: after = 0.10000000149011612
loadedmetadata: before = 0.10000000149011612
loadedmetadata: after = 19.773332595825195
loadeddata: before = 19.773332595825195
loadeddata: after = 0.10000000149011612
canplay: before = 0.10000000149011612
canplay: after = 19.773332595825195
And sometimes it never even seems to set at all;
durationchange: before = 50.66666793823242
durationchange: after = 50.66666793823242
loadedmetadata: before = 50.66666793823242
loadedmetadata: after = 50.66666793823242
loadeddata: before = 50.66666793823242
loadeddata: after = 50.66666793823242
canplay: before = 50.66666793823242
canplay: after = 50.66666793823242
This seems similar to the issue here but there didn't seem to be any resolution. Has anyone encountered this issue on iPhone before?

TL;DR: change currentTime on the loadeddata event. This works for audio too.
It looks like Safari (and the problem is still appearing for me on Safari 11.1) is Safari will not allow currentTime to be changed when a video is first loaded IF it hasn't loaded a frame for that currentTime yet. The bigger problem: the wrong solution can break Chrome (and likely other browsers too).
Fortunately, we have a lot of events we can listen for while media is loading:
During the loading process of an audio/video, the following events occur, in this order:
loadstart
durationchange
loadedmetadata
loadeddata
progress
canplay
canplaythrough
-W3Schools (I know it's not a preferred source, but I couldn't find the same info on MDN)
I tried adjusting currentTime on different events, but on the first 3 events, Safari would move the time back to 0; and on 5 and 6 it seemed to prevent the video from playing in Chrome, because it would get stuck at currentTime (which I could've worked around, but I thought there was a better solution).
(I didn't want to have to load the whole file to go to the right spot, because I want to support hefty videos. So canplaythrough wasn't an option for me.)
The description for the loadeddata event reads:
The loadeddata event is fired when the first frame of the media has finished loading.
-MDN
When I changed currentTime on loadeddata, Safari could tell that the frame was loaded and available, and would update correctly. It also wouldn't cause Chrome to freeze in a single spot while playing.
The problem and solution are identical for audio.

From my findings the issue seems to be that on iPhone only (iPad works fine) the currentTime property will not be set correctly until the "canplaythrough" event, however changing the currentTime at that point will cause a noticeable hiccup. The solution for that would be to intentionally pause the video after calling load...
myVideo.load();
myVideo.pause();
...and then call play in the event when the time has reset.
The second problem however is when the duration of the new movie is shorter then the currentTime position. In this case not only does currentTime fail to set but "canplaythrough" is never called, and QT just sits at the end of the video doing nothing.
I discovered the solution to both problems was to force a secondary load if the currentTime was not reset in the event BEFORE "canplaythrough". Its a bit round about with the timer callback but it seems to do the trick;
var myVideo = document.getElementById("video1");
myVideo.addEventListener("canplay", function() {
console.log(" canplay: before = " + myVideo.currentTime);
myVideo.currentTime = 0.1;
console.log(" canplay: after = " + myVideo.currentTime);
if( myVideo.currentTime < 1 ) {
myVideo.play();
}
else {
myVideo.load();
myVideo.pause();
setTimeout(checkStarted, 500);
}
}, false);
function checkStarted()
{
console.log(" checkStarted called");
myVideo.play();
}

I had to set the preload attribute to metadata (on the HTML of the main video element) and set the currentTime of the video element within the loadedmetadata event listener.
<video id="myVideo" preload="metadata">
<source src="/path/to/video" type="video/mp4">
</video>
JS
VideoEl.addEventListener('loadedmetadata', VideoMetaDataLoaded);
function VideoMetaDataLoaded() {
VideoEl.currentTime = newTime;
}

Below is my Angular/Ionic solution to restore the video position. It works on IOS, Android, Chrome and Safari.
HTML:
<video preload="metadata"
poster="{{ resource.thumbnail_file }}"
playsinline webkit-playsinline
#videos>
...
</video>
Typescript:
#ViewChildren('videos') videos: QueryList<any>;
videoCache: Map<number, number> = new Map<number, number>();
private restoreVideoPositions() {
var context = this;
setTimeout(() => {
this.videos.forEach(function (item, idx) {
var video = item.nativeElement;
var currentTime = context.videoCache.get(video.id);
if (currentTime != null && currentTime > 0) {
console.log('add listener', currentTime);
video.addEventListener("loadeddata", function () {
if (video.readyState >= 3) {
video.currentTime = currentTime;
// video.play();
}
});
video.load();
}
});
}, 0);
}
private storeVideoPositions() {
var context = this;
this.videoCache.clear()
this.videos.forEach(function (item, idx) {
var video = item.nativeElement;
video.pause();
context.videoCache.set(video.id, video.currentTime)
});
}

I used a combination of the last two answers here, but had to reduce the ready state limit to 2 (HTMLVideoElement.prototype.HAVE_CURRENT_DATA) as the loadeddata event on iOS would often never come in higher than 2.

With help from the other answers here I came up with the next solution:
HTMLVideoElement.prototype.playFromTime = function(currentTime){
let that = this;
that.load();
that.pause();
that.currentTime = currentTime;
let loadedMetadata;
loadedMetadata = function(event){
that.currentTime = currentTime;
that.removeEventListener("loadedmetadata", loadedMetadata);
}
if(that.currentTime !== currentTime){
that.addEventListener("loadedmetadata", loadedMetadata);
}
that.play();
}
//usage example:
myVidElement.playFromTime(20);
Which in my situation works on an iPhone on Safari, Safari on the desktop, Firefox on macOS, Chrome on macOS. That's all I have tested for now.

Had the same problem with a play video on scroll with React. The following solved it for me.
useEffect(() => {
videoRef.current.load();
}, []);
const { render } = ReactDOM;
const { useState, useEffect, useRef } = React;
const Video = ({ src, height, length }) => {
const [currentTime, setCurrentTime] = useState(0);
const videoRef = useRef(null);
useEffect(() => {
// This is needed for IOS
videoRef.current.load();
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const getCurrentTime = () => {
const percentScrolled = window.scrollY / (height - window.innerHeight);
return length * percentScrolled;
};
const handleScroll = (e) => {
const time = getCurrentTime();
videoRef.current.currentTime = time;
setCurrentTime(time);
};
return (
<div style={{ height }}>
<p>Time: {currentTime.toFixed(2)}s</p>
<video ref={videoRef} muted playsInline>
<source src={src} type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
);
};
const App = () => {
return (
<Video
length={5}
height={3000}
src="https://lqez.github.io/js/airpodsvf/video.mp4"
/>
);
};
render(<App />, document.getElementById("root"));
body {
background: black;
}
p {
color: slategrey;
top: 0;
position: fixed;
z-index: 1;
padding: 20px;
}
video {
top: 60px;
position: fixed;
width: 100%;
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

this is a problem with chrome on local assets. happened with me. when i was serving video from local assets and setting time it resets the video to 0.
so the solution that worked for me was serving the video from s3 bucket.

Related

Ctrl+f (find) in Chrome app

Is it somehow possible to enable the browser feature Ctrl+F (find in the current view) in a chrome app?
I am talking about an app I am writing myself and I am really missing that feature.
I thought an example might be nice. I wanted this too.
Here is what I came up with from the ideas above.
I edited the Kiosk App sample from Google with these changes:
HTML:
enter code here
CSS:
#live-search{
display:none;
}
.text-input{
right: 25px;
z-index: 10000;
position: absolute;
border: #000 solid 1px;
}
JS:
window.onresize = doLayout;
var isLoading = false;
var webview //made global so that I can access it easier everywhere.
onload = function() {//existing function, added more to it.
webview = document.querySelector('webview');
webview.addEventListener('consolemessage', function(e) {
//this lets the webview send to the console so that I can see why something doesn't work.
console.log('Guest page log at '+ e.line + ", ", e.message);
});
doLayout();
$().ready(function()//using jquery onload here.
{
var ctrlDown = false;
var ctrlKey = 17, fKey = 70;
$(document).keydown(function(e)//detect CTRL key.
{
if (e.keyCode == ctrlKey) ctrlDown = true;
}).keyup(function(e)
{
if (e.keyCode == ctrlKey) ctrlDown = false;
});
$(document).keydown(function(e)//detect CTRL+f
{
if (ctrlDown && e.keyCode == fKey){
$("#live-search .text-input").val("").focus();//clear search box
$("#live-search").toggle();
};
});
$("#live-search .text-input").keyup(function(e){//typed in search, so search.
webview.executeScript(
{code:
'window.find("'+ $("#live-search .text-input")[0].value +'",false, false, true);'
});
});
});
JS in one other function :
function handleLoadStop(event) {//hide the search when they go to another page.
$("#live-search").hide();
You can't "enable the browser feature" because a Chrome App window is not a browser. True, it's it's always initially loaded from an HTML file, but that doesn't make it a browser. What you have to do is set a handler for the keyboard event (a technique not unique to Chrome Apps) and then in that handler do what you want. You can process the DOM representing the contents of the window to search for something if you like, but this is entirely up to your own JavaScript coding.
If you are using WebView in your application to embed some page, so you can use this:
var wv = document.querySelector('webview');
wv.executeScript({code: "window.find('Some text',false, false, true);"})

Get artwork_url from Soundcloud API and show album covers in custom SC/SM2 player

I've been trying to sort out how artwork_url can be used from soundclouds API in order to output each cover into this custom player, and have each appropriate thumb next to its own track in the playlist?
I understand that I need to use the artwork_url property, however I do not understand how this is achieved, nor how to integrate it into this particular custom player plugin.
Any code examples in particular and/or help is highly appreciated!
Note: Also would be nice to be able to control the "size" of the artwork as well through other means then just CSS.
Best
EDIT #1
I switched the Soundcloud Custom Player on Heroku since after I was able to get it up and running I discovered it to have a much faster load time in contrast to the original player I cited above (even though that one is still quite awesome)...
Im still posed with the same task now however as before - How to add album art to the script and output accordingly?
Pasted below is the Heroku player:
// # SoundCloud Custom Player
// Make sure to require [SoundManager2](http://www.schillmania.com/projects/soundmanager2/) before this file on your page.
// And set the defaults for it first:
soundManager.url = 'http://localhost:8888/wp-content/themes/earpeacerecords/swf';
soundManager.flashVersion = 9;
soundManager.useFlashBlock = false;
soundManager.useHighPerformance = true;
soundManager.wmode = 'transparent';
soundManager.useFastPolling = true;
// Wait for jQuery to load properly
$(function(){
// Wait for SoundManager2 to load properly
soundManager.onready(function() {
// ## SoundCloud
// Pass a consumer key, which can be created [here](http://soundcloud.com/you/apps), and your playlist url.
// If your playlist is private, make sure your url includes the secret token you were given.
var consumer_key = "915908f3466530d0f70ca198eac4288f",
url = "http://soundcloud.com/poe-epr/sets/eprtistmix1";
// Resolve the given url and get the full JSON-worth of data from SoundCloud regarding the playlist and the tracks within.
$.getJSON('http://api.soundcloud.com/resolve?url=' + url + '&format=json&consumer_key=' + consumer_key + '&callback=?', function(playlist){
// I like to fill out the player by passing some of the data from the first track.
// In this case, you'll just want to pass the first track's title.
$('.title').text(playlist.tracks[0].title);
// Loop through each of the tracks
$.each(playlist.tracks, function(index, track) {
// Create a list item for each track and associate the track *data* with it.
$('<li>' + track.title + '</li>').data('track', track).appendTo('.tracks');
// * Get appropriate stream url depending on whether the playlist is private or public.
// * If the track includes a *secret_token* add a '&' to the url, else add a '?'.
// * Finally, append the consumer key and you'll have a working stream url.
url = track.stream_url;
(url.indexOf("secret_token") == -1) ? url = url + '?' : url = url + '&';
url = url + 'consumer_key=' + consumer_key;
// ## SoundManager2
// **Create the sound using SoundManager2**
soundManager.createSound({
// Give the sound an id and the SoundCloud stream url we created above.
id: 'track_' + track.id,
url: url,
// On play & resume add a *playing* class to the main player div.
// This will be used in the stylesheet to hide/show the play/pause buttons depending on state.
onplay: function() {
$('.player').addClass('playing');
$('.title').text(track.title);
},
onresume: function() {
$('.player').addClass('playing');
},
// On pause, remove the *playing* class from the main player div.
onpause: function() {
$('.player').removeClass('playing');
},
// When a track finished, call the Next Track function. (Declared at the bottom of this file).
onfinish: function() {
nextTrack();
}
});
});
});
// ## GUI Actions
// Bind a click event to each list item we created above.
$('.tracks li').live('click', function(){
// Create a track variable, grab the data from it, and find out if it's already playing *(set to active)*
var $track = $(this),
data = $track.data('track'),
playing = $track.is('.active');
if (playing) {
// If it is playing: pause it.
soundManager.pause('track_' + data.id);
} else {
// If it's not playing: stop all other sounds that might be playing and play the clicked sound.
if ($track.siblings('li').hasClass('active')) { soundManager.stopAll(); }
soundManager.play('track_' + data.id);
}
// Finally, toggle the *active* state of the clicked li and remove *active* from and other tracks.
$track.toggleClass('active').siblings('li').removeClass('active');
});
// Bind a click event to the play / pause button.
$('.play, .pause').live('click', function(){
if ( $('li').hasClass('active') == true ) {
// If a track is active, play or pause it depending on current state.
soundManager.togglePause( 'track_' + $('li.active').data('track').id );
} else {
// If no tracks are active, just play the first one.
$('li:first').click();
}
});
// Bind a click event to the next button, calling the Next Track function.
$('.next').live('click', function(){
nextTrack();
});
// Bind a click event to the previous button, calling the Previous Track function.
$('.prev').live('click', function(){
prevTrack();
});
// ## Player Functions
// **Next Track**
var nextTrack = function(){
// Stop all sounds
soundManager.stopAll();
// Click the next list item after the current active one.
// If it does not exist *(there is no next track)*, click the first list item.
if ( $('li.active').next().click().length == 0 ) {
$('.tracks li:first').click();
}
}
// **Previous Track**
var prevTrack = function(){
// Stop all sounds
soundManager.stopAll();
// Click the previous list item after the current active one.
// If it does not exist *(there is no previous track)*, click the last list item.
if ( $('li.active').prev().click().length == 0 ) {
$('.tracks li:last').click();
}
}
});
});
EDIT #2
So I strangely was able to work something out... I have no clue if its semantically correct however...
$.getJSON('http://api.soundcloud.com/resolve?url=' + url + '&format=json&consumer_key=' + consumer_key + '&callback=?', function(playlist){
// I like to fill out the player by passing some of the data from the first track.
// In this case, you'll just want to pass the first track's title.
$('.title').text(playlist.tracks[0].title);
$('.album_art').attr('src', playlist.artwork_url);
// Loop through each of the tracks
$.each(playlist.tracks, function(index, track) {
// Create a list item for each track and associate the track *data* with it.
$('<li>' + '<img src="' + playlist.artwork_url + '">' + track.title + '</li>').data('track', track).appendTo('.tracks');
// * Get appropriate stream url depending on whether the playlist is private or public.
// * If the track includes a *secret_token* add a '&' to the url, else add a '?'.
// * Finally, append the consumer key and you'll have a working stream url.
url = track.stream_url;
(url.indexOf("secret_token") == -1) ? url = url + '?' : url = url + '&';
url = url + 'consumer_key=' + consumer_key;
// ## SoundManager2
// **Create the sound using SoundManager2**
soundManager.createSound({
// Give the sound an id and the SoundCloud stream url we created above.
id: 'track_' + track.id,
url: url,
// On play & resume add a *playing* class to the main player div.
// This will be used in the stylesheet to hide/show the play/pause buttons depending on state.
onplay: function() {
$('.player').addClass('playing');
$('.title').text(track.title);
},
onresume: function() {
$('.player').addClass('playing');
},
// On pause, remove the *playing* class from the main player div.
onpause: function() {
$('.player').removeClass('playing');
},
// When a track finished, call the Next Track function. (Declared at the bottom of this file).
onfinish: function() {
nextTrack();
}
});
});
EDIT #3
Below is the HTML and CSS markup that works with the player for better clarification...
HTML
<div class='title'></div>
<a class='prev'>Previous</a>
<a class='play'>Play</a>
<a class='pause'>Pause</a>
<a class='next'>Next</a>
</div>
CSS
/*
-------------------------------------------------------------------------
Soundcloud Player
-------------------------------------------------------------------------
*/
#sticky_header #sticky_content .player {
height: 570px;
overflow: hidden;
}
#sticky_header #sticky_content .tracks {
}
#sticky_header #sticky_content .tracks li {
cursor: pointer;
height: 40px;
text-align: left;
}
#sticky_header #sticky_content .tracks li img.album_art {
width: 40px;
height: 40px;
border-radius: 5px;
margin-right: 15px;
}
#sticky_header #sticky_content .title {
}
#sticky_header #sticky_content .prev {
}
#sticky_header #sticky_content .play {
display: block;
}
#sticky_header #sticky_content .playing .play {
display: none;
}
#sticky_header #sticky_content .pause {
display: none;
}
#sticky_header #sticky_content .playing .pause {
display: block;
}
#sticky_header #sticky_content .next {}
to get an image you can use this code:
//get element by id from your iframe
var widget = SC.Widget(document.getElementById('soundcloud_widget'));
widget.getCurrentSound(function(music){
artwork_url = music.artwork_url.replace('-large', '-t200x200');
$('#song1').css('background', 'url(\"'+artwork_url+'\") ');
});
normaly you get a link with "-large" at the end and the size is 100x100. If you want other sizes you have to change the end with ".replace" like I did. A list with available sizes can you find here:
https://developers.soundcloud.com/docs/api/reference#tracks
(my size 200x200 is not listed but works. Maybe there are more sizes like every hundred px.)
at the moment the code works only for the actual playing song. For me it's not a solution, because i need all images from my playlist.
Here's where iterating over the tracks retrieved from the API happeninng:
// Loop through each of the tracks
$.each(playlist.tracks, function(index, track) {
// Create a list item for each track and associate the track *data* with it.
$('<li>' + track.title + '</li>').data('track', track).appendTo('.tracks');
Inside of the iterator function you can now access track.artwork_url and possibly set it as a background image or perhaps background for some element, maybe something like:
$('<li><img src=" + track.artwork_url + "></img>' + track.title + '</li>').data('track', track).appendTo('.tracks');
I hope this helps.
UPD. In your updated code, you should refer to track.artwork_url instead of playlist – then you'll get each track's individual artwork.

Drag and Drop into Fabric.js canvas

How can I drop items (like image, or other object from other canvas) into canvas which is managed by fabricjs? I have found many examples how to move items inside canvas but I would like to drag and drop item from outer element into canvas.
Since you asked for an example and I haven't tried it out myself yet, here goes:
Example Fiddle
Markup
<div id="images">
<img draggable="true" src="http://i.imgur.com/8rmMZI3.jpg" width="250" height="250"></img>
<img draggable="true" src="http://i.imgur.com/q9aLMza.png" width="252" height="295"></img>
<img draggable="true" src="http://i.imgur.com/wMU4SFn.jpg" width="238" height="319"></img>
</div>
<div id="canvas-container">
<canvas id="canvas" width="800" height="600"></canvas>
</div>
JS Breakdown
1. Fabric.canvas instance
First we want our canvas, of course:
var canvas = new fabric.Canvas('c');
2. Feature Detection (optional)
Not sure this is necessary, since the fact that you have a canvas makes it very likely that the browser has Drag and Drop as well. Were you to use it, you can do so like this, using Modernizr:
if (Modernizr.draganddrop) {
// Browser supports HTML5 DnD.
// Bind the event listeners for the image elements
// Bind the event listeners for the canvas
} else {
// Replace with a fallback to a library solution.
alert("This browser doesn't support the HTML5 Drag and Drop API.");
}
3. Events
Again, unlike the source article I below, the source and target elements are different (in that articles's example, you just move divs around within the same parent container), so I failed to notice that some of the events are meant for the element being dragged, but most are bound to the element into which you are dropping.
NOTE: I know this is technically a question about Fabric.js, but it's really kind of a question about Drag and Drop in the context of adding objects to a <canvas> with Fabric.js, which is why I'm going a bit more in depth about the DnD stuff now.
For the <img>
dragstart (I added a class here to lower the opacity)
dragend (and removed that class here)
For #canvas-container:
dragenter (added a class to give the canvas container that nifty dotted line)
dragover: Here you can set the event.dataTransfer.dropEffect property to show one of the native cursor types. The default would be 'move' here, but I set it to 'copy' since I don't actually remove the <img> element (in fact in the fiddle you can, for example create several McClures).
dragleave (removed the dotted line here)
drop: The handler for this event creates and adds the fabric.Image object (see the fiddle).
if (Modernizr.draganddrop) {
// Browser supports HTML5 DnD.
// Bind the event listeners for the image elements
var images = document.querySelectorAll('#images img');
[].forEach.call(images, function (img) {
img.addEventListener('dragstart', handleDragStart, false);
img.addEventListener('dragend', handleDragEnd, false);
});
// Bind the event listeners for the canvas
var canvasContainer = document.getElementById('canvas-container');
canvasContainer.addEventListener('dragenter', handleDragEnter, false);
canvasContainer.addEventListener('dragover', handleDragOver, false);
canvasContainer.addEventListener('dragleave', handleDragLeave, false);
canvasContainer.addEventListener('drop', handleDrop, false);
} else {
// Replace with a fallback to a library solution.
alert("This browser doesn't support the HTML5 Drag and Drop API.");
}
Sources:
HTML5 Rocks - Native HTML5 Drag and Drop
Modernizr
Web Platform Docs > DOM > Properties - dropEffect
Web Platform Docs > DOM > Events
dragstart
dragend
dragenter
dragover
dragleave
drop
I had gone through fiddle of #natchiketa, And fixed the problem , just check this fiddle..
http://jsfiddle.net/Ahammadalipk/w8kkc/185/
window.onload = function () {
var canvas = new fabric.Canvas('canvas');
/*
NOTE: the start and end handlers are events for the <img> elements; the rest are bound to
the canvas container.
*/
function handleDragStart(e) {
[].forEach.call(images, function (img) {
img.classList.remove('img_dragging');
});
this.classList.add('img_dragging');
}
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault();
}
e.dataTransfer.dropEffect = 'copy';
return false;
}
function handleDragEnter(e) {
this.classList.add('over');
}
function handleDragLeave(e) {
this.classList.remove('over');
}
function handleDrop(e) {
if (e.stopPropagation) {
e.stopPropagation(); // stops the browser from redirecting.
}
var img = document.querySelector('#images img.img_dragging');
var newImage = new fabric.Image(img, {
width: img.width,
height: img.height,
// Set the center of the new object based on the event coordinates relative
// to the canvas container.
left: e.layerX,
top: e.layerY
});
newImage.hasControls = newImage.hasBorders = false;
canvas.add(newImage);
return false;
}
function handleDragEnd(e) {
// this/e.target is the source node.
[].forEach.call(images, function (img) {
img.classList.remove('img_dragging');
});
}
if (Modernizr.draganddrop) {
var images = document.querySelectorAll('#images img');
[].forEach.call(images, function (img) {
img.addEventListener('dragstart', handleDragStart, false);
img.addEventListener('dragend', handleDragEnd, false);
});
var canvasContainer = document.getElementById("canvas-container");
canvasContainer.addEventListener('dragenter', handleDragEnter, false);
canvasContainer.addEventListener('dragover', handleDragOver, false);
canvasContainer.addEventListener('dragleave', handleDragLeave, false);
canvasContainer.addEventListener('drop', handleDrop, false);
} else {
alert("This browser doesn't support the HTML5 Drag and Drop API.");
}
}
Thanks
Well, the question is quite old^^
I have updateted the fiddle, no it will work in Firefox as well.
Fiddle
function handleDrop(e) {
// this / e.target is current target element.
e.preventDefault(); //I've altert this line for FireFox
As far as I understand, drag and drop is not already provided by fabricjs,
fiddling it will be quite interesting.
Well I m new to javascript and fabricJS but I think this fiddle might help
fiddle
html code
<div class="img_cont">
<img class="img" id="ele1" draggable="true" ondragstart="dragElement(event)" src="https://webkit.org/wp-content/uploads/STP-300x300.png">
<img class="img" id="ele2" draggable="true" ondragstart="dragElement(event)" src="https://webkit.org/wp-content/uploads/ephy-webkit-graphic.png">
<img class="img" id="ele3" draggable="true" ondragstart="dragElement(event)" src="https://res.cloudinary.com/css-tricks/image/upload/w_600,q_auto,f_auto/buysellads/uu/7/112766/1646327381-MC_CSSTricks_Logo_600x600-_1_.png">
<img class="img" id="ele4" draggable="true" ondragstart="dragElement(event)" src="https://miro.medium.com/max/1400/1*9hd_8qR0CMZ8L0pVbFLjDw.png">
</div>
<br>
<div id="canvas_cont" ondragover="allowDrop(event)" ondrop="dropElement(event)">
<canvas id="canvas" width="650" height="350" ></canvas>
</div>
javascript code
// allowDrop function called on ondragover event.
function allowDrop(e) {
e.preventDefault();
}
//dragElement function called on ondrag event.
function dragElement(e) {
e.dataTransfer.setData("id", e.target.id); //transfer the "data" i.e. id of the target dragged.
}
//Initializing fabric canvas on window load event.
var canvas;
window.onload = function(){
canvas = new fabric.Canvas(document.getElementById("canvas"));
}
//dropElement function called on ondrop event.
function dropElement(e) {
e.preventDefault();
var data = e.dataTransfer.getData("id"); //receiving the "data" i.e. id of the target dropped.
var imag = document.getElementById(data); //getting the target image info through its id.
var img = new fabric.Image(imag, { //initializing the fabric image.
left: e.layerX - 80, //positioning the target on exact position of mouse event drop through event.layerX,Y.
top: e.layerY - 40,
});
img.scaleToWidth(imag.width); //scaling the image height and width with target height and width, scaleToWidth, scaleToHeight fabric inbuilt function.
img.scaleToHeight(imag.height);
canvas.add(img);
}
The accepted answer no longer works.
This is for drag and drop from desktop using the dataTransfer interface.
canvas.on('drop', function(event) {
// prevent the file to open in new tab
event.e.stopPropagation();
event.e.stopImmediatePropagation();
event.e.preventDefault();
// Use DataTransfer interface to access the file(s)
if(event.e.dataTransfer.files.length > 0){
var files = event.e.dataTransfer.files;
for (var i = 0, f; f = files[i]; i++) {
// Only process image files.
if (f.type.match('image.*')) {
// Read the File objects in this FileList.
var reader = new FileReader();
// listener for the onload event
reader.onload = function(evt) {
// put image on canvas
fabric.Image.fromURL(evt.target.result, function(obj) {
obj.scaleToHeight(canvas.height);
obj.set('strokeWidth',0);
canvas.add(obj);
});
};
// Read in the image file as a data URL.
reader.readAsDataURL(f);
}
}
}
});
Resources
https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop

Connecting a simple gain node to Web Audio API file buffers

I can't seem to understand why this isn't working. I have no errors.I have done this using oscillators and it works fine. FYI this is a continuation of this thread:
Using Web Audio API to assign two sounds to two Divs and play each independently via a click event with javascript
Code:
<div id="divElement"></div>
<style>
#divElement{
background-color: orange;
width:70px;
height:100px;
left:143px;
}
</style>
<script>
var context = new webkitAudioContext(),
savedBuffer;
var playAudioFile = function () {
var source = context.createBufferSource();
var gainNode = context.createGainNode();
source.buffer = savedBuffer;
// START OF PROBLEM AREA: Gain not working but file still plays with no errors.
source.connect(gainNode);
gainNode.connect(context.destination);
gainNode.gain = 0;
// END OF PROBLEM AREA
source.noteOn(0); // Play sound immediately
};
var request = new XMLHttpRequest();
request.open('get', 'audio/A.mp3', true);
request.responseType = 'arraybuffer';
request.onload = function () {
context.decodeAudioData(request.response,
function(incomingBuffer) {
savedBuffer = incomingBuffer;
var divElement = document.getElementById("divElement");
divElement.addEventListener("click", playAudioFile , false);
}
);
};
request.send();
</script>
Try gainNode.gain.value = 0 instead. gainNode.gain is an AudioGain object, which has the attribute value.
http://www.w3.org/TR/webaudio/#AudioGainNode

Catching play event in video on mobile safari

I have an iOS app that contains a lot of local web content. Some of that content is video/audio. I have a click event attached to the video and audio tags that fires an analytics url that I'm going to catch in the UIWebView. The problem is that click event doesn't register. I'm assuming this is because iOS replaces the video with its own special movie player. How do I catch the play event so I can do something with it. Here's my jQuery code for the click event.
function videoClick() {
$("video").click(function () {
alert("here");
//document.location = "ignoretap:///";
var videoTitle = $(this).attr("data-mediatitle");
var videoSrc = $(this).children("source").attr("src");
if (videoTitle != null && videoTitle.length > 0) {
document.location = "analytics:///" + "page" + videoTitle;
}
else {
document.location = "analytics:///" + "page" + videoSrc;
}
});
}
Here's my html for the video
<video width="100%" controls="controls" data-mediatitle="testing">
<source src="StoryOfIyal.mp4" type="video/mp4" poster="IyalVideo.htm-iyalvideoscreenshot_lg.jpg"></source>
Your Browser does not support the video tag
</video>
Figured it out. You have to bind to the "play" event in jQuery, not the click event. Here's the updated function.
function videoClick() {
$("video").bind('play', function () {
alert("here");
//document.location = "ignoretap:///";
var videoTitle = $(this).attr("data-mediatitle");
var videoSrc = $(this).children("source").attr("src");
if (videoTitle != null && videoTitle.length > 0) {
document.location = "analytics:///" + "page" + videoTitle;
}
else {
document.location = "analytics:///" + "page" + videoSrc;
}
});
}