On my page I load some tracks in a music player ( http://dev.upcoming-djs.com ) on page load.
Which is working fine.
However when people go to the following URL: http://dev.upcoming-djs.com/track/1/artist/title I would like to load another track in the player on page load.
Currently the JS code for the player is:
var tracks = [];
tracks.push({'url':trackurl, 'title':trackurl});
$('.upcoming-player-container').theplayer({
links: tracks // contains an array with url's
});
Which is static.
What would be the best way (not meant to be subjective, just don't know how to rephrase it) of making it dynamic.
I was thinking of adding some hyperlinks in a (hidden) container on the page which I retrieve with JS.
Is this a good way to do it? (Since I think Google might add the text to the search results).
Or perhaps there is a better way of doing it?
Assuming that the player will play the tracks in order, here's some code that will shift the track specified by the URL /track/##/artist/title to the front of the playlist, presumably playing it first on page load:
var tracks = [],
trackMatch
trackPlay;
tracks.push({'url': trackurl,'title': trackurl});
// tracks.push etc...
// find the track number
trackMatch = window.location.href.match(/track\/(\d+)/i);
// if found store as integer
if (trackMatch) {
trackPlay = parseInt(trackMatch[1], 10);
}
// if found move selected track to front of tracks array
if (trackPlay) {
tracks = [tracks[trackPlay - 1]]
.concat(tracks.slice(0, trackPlay - 1))
.concat(tracks.slice(trackPlay));
}
$('.upcoming-player-container').theplayer({
links: tracks // contains an array with url's
});
Related
I am trying to make a basic Instagram web scraper, both art inspiration pictures and just generally trying to boost my knowledge and experience programming.
Currently the issue that I am having is that Casper/Phantomjs can't detect higher res images from the srcset, and I can't figure out a way around this. Instagram has their srcsets provide 640x640, 750x750, and 1080x1080 images. I would obviously like to retrieve the 1080, but it seems to be undetectable by any method I've tried so far. Setting the viewport larger does nothing, and I can't retrieve the entire source set through just getting the HTML and splitting it where I need it. And as far as I can tell, there is no other way to retrieve said image than to get it from this srcset.
Edit
As I was asked for more details, here I go. This is the code I used to get the attributes from the page:
function getImages() {
var scripts = document.querySelectorAll('._2di5p');
return Array.prototype.map.call(scripts, function (e) {
return e.getAttribute('src');
});
}
Then I do the standard:
casper.waitForSelector('div._4rbun', function() {
this.echo('...found selector ...try getting image srcs now...');
imagesArray = this.evaluate(getImages);
imagesArray.forEach(function (item) {
console.log(item);
However, all that is returned is the lowest resolution of the srcset. Using this url, for example, (https://www.instagram.com/p/BhWS4csAIPS/?taken-by=kasabianofficial) all that is returned is https://instagram.flcy1-1.fna.fbcdn.net/vp/b282bb23f82318697f0b9b85279ab32e/5B5CE6F2/t51.2885-15/s640x640/sh0.08/e35/29740443_908390472665088_4690461645690896384_n.jpg, which is the lowest resolution (640x640) image in the srcset. Ideally, I'd like to retrieve the https://instagram.flcy1-1.fna.fbcdn.net/vp/8d20f803e1cb06e394ac91383fd9a462/5B5C9093/t51.2885-15/e35/29740443_908390472665088_4690461645690896384_n.jpg which is the 1080x1080 image in the srcset. But I can't. There's no way to get that item as far as I can tell. It's completely hidden.
I found a way around it in Instagram's case. Instagram puts the source picture in a meta tag within the head. So, using the code I'll paste below, you can call all of the meta tags and then sort out which one is the source picture by checking if "og:image" is retrieved.
function getImages() {
var scripts = document.querySelectorAll('meta[content]');
return Array.prototype.map.call(scripts, function (e) {
return e.getAttribute('property') + " " + e.getAttribute('content');
});
}
And this is the way to sort the meta tags into only having the original image in its native resolution.
this.echo('...found selector ...try getting image srcs now...');
imagesArray = this.evaluate(getImages);
imagesArray.forEach(function (item) {
if (typeof item == "string" && item.indexOf('og:image') > -1) {
Edit: Unfortunately this only works for single image posts on Instagram (the site I'm trying to scrape) so this unfortunately does me no goo. The values within the meta tags don't change even if you load the next image in the post. I'm leaving this up though in case anyone else could use it, but it's not ideal for my own use case.
Yes indeed PhantomJS doesn't seem to support srcset, its Webkit engine is very old.
But to be fair, all the metadata related to the page is out in the open in the HTML as JSON in window._sharedData variable.
If you want to use a headless browser (and not parse it with any server-side language) you can do this:
var imgUrl = page.evaluate(function(){
return window._sharedData.entry_data.PostPage[0].graphql.shortcode_media.display_resources[2].src;
});
https://instagram.fhen2-1.fna.fbcdn.net/vp/8d20f803e1cb06e394ac91383fd9a462/5B5C9093/t51.2885-15/e35/29740443_908390472665088_4690461645690896384_n.jpg
Solution: So my solution was to use slimerjs. If I run the js file through "casperjs --engine=slimerjs fileName.js", I can retrieve srcsets in full. So if I say use this code:
function getImgSrc() {
var scripts = document.querySelectorAll("._2di5p");
return Array.prototype.map.call(scripts, function (e) {
return e.getAttribute("srcset");
});
}
on this url (https://www.instagram.com/p/BhWS4csAIPS/?taken-by=kasabianofficial) I will get (https://instagram.flcy1-1.fna.fbcdn.net/vp/b282bb23f82318697f0b9b85279ab32e/5B5CE6F2/t51.2885-15/s640x640/sh0.08/e35/29740443_908390472665088_4690461645690896384_n.jpg 640w,https://instagram.flcy1-1.fna.fbcdn.net/vp/b4eebf94247af02c63d20320f6535ab4/5B6258DF/t51.2885-15/s750x750/sh0.08/e35/29740443_908390472665088_4690461645690896384_n.jpg 750w,https://instagram.flcy1-1.fna.fbcdn.net/vp/8d20f803e1cb06e394ac91383fd9a462/5B5C9093/t51.2885-15/e35/29740443_908390472665088_4690461645690896384_n.jpg 1080w) as the result.
This is what I wanted as it means I can scrape those 1080 images. Sorry for this messy page, but I wanted to leave my trail of steps to any of those who might be trying like me.
I am using the HTML5 Widget Api to take a SoundCloud playlist with 14 songs and store the information about every song in an array.
I use the .getSounds method as following:
function loadTracks(){
myPlayer.player.getSounds(function(ret){
myPlayer.playlistInfo = ret;
});
}
It correctly returns an array with 14 spots. The first 5 spots contain exactly what i want, but the last 9 have different information that does not seem to be related to the song.
this is how the returned array looks like
I could recreate the problem with different playlists. I only get the correct information for the first 5 songs.
Does anyone has and idea on how to solve this? I set up a codepen for this
Thanks.
I was struggling with the same problem. For me it looks like SC.Widget.Events.READY is sometimes firing before the Widget is really completely ready.
A safe solution would be to loop through a function and check the array for its completeness until all the data you need is there. In my example on Code Pen I check the array every 200 ms for all titles and break out of the loop on success.
It looks like the data is all valid - resource type: sound, type: track - but it looks like the API is not returning the full set of information for each song in the playlist beyond the fifth. It's only returning the artwork URL and extended information for the first 5, but I believe the rest of the songs are still accessible by their id. If you need the extra Information, you may have to query the SoundCloud API for each specific song beyond the fifth index (if length > 5), which will probably return the full info for each song. You'll have to do many queries with this method, however
The real answer... It is not possible. getSounds wont return all the track info for a playlist at once like you expect it to. The reasons:
The Widget API is an half-baked, insufficient, poorly-documented, abandoned API.
The full SoundCloud API has more functionality, but has been closed off for many many years, and will likely never return.
SoundCloud has no real developer support at all anymore.
SoundCloud as a whole company seems to have been circling the drain financially for several years (I recall several years ago they almost shut down before getting a new CEO or something). I speculate that this is causing the above shortcomings.
But I didn't create a new answer just to say that.
I need a media player for a redesign of my website. The SoundCloud widget is so ugly and incomplete and inflexible, but SoundCloud as a service already provides streaming audio and recording of song plays/downloads/comments, which would be a big effort to reimplement. Also for some reason, SoundCloud is the standard for embedding sharable audio on websites (look at any sample library demo page). Bandcamp has a widget too, but it can only do widgets by albums and not playlists, and also doesn't show things like play numbers or supporters, and is also completely un-customizable. So I really wanted to find a way to make this dumb Widget API work.
Here is a REALLY HACKY AND UGLY way I think I found that hopefully works consistently. Use with caution.
It literally just goes through each track with .next() as fast as it can, calling getCurrentSound repeatedly on each until it loads and returns the full song info.
// once you know how many tracks are in the widget...
const fullTracks = [];
// do the following process same amount of times as number of tracks
for (let track = 0; track < tracks.length; track++) {
// make periodic attempts (up to a limit) to get the full info
for (let tries = 0; tries < 100; tries++) {
// promisify getCurrentSound (doesn't handle error)
const sound = await new Promise((resolve) => player.getCurrentSound(resolve));
// check if full info has loaded for current sound yet
if (sound.artwork_url) {
// record full info
fullTracks.push(sound);
// stop re-trying
break;
} else
// wait a bit before re-trying
await new Promise((resolve) => window.setTimeout(resolve, 10));
}
// move to next track
player.next();
}
// reverse the process; go all the way back to first track again
for (let track = 0; track < tracks.length; track++)
player.prev();
On my machine and internet connection, for 13 tracks, this takes about 300ms. It also throws a bunch of horrible errors in the console that you can't try/catch to suppress.
I have an application where (non-IT) users create and maintain product pages (the products are music CDs), so the pages contain usual blurb about the CD and a track-listing.
We want to insert an mp3 music player into each track, but need to keep the in-line 'code' that that usrs add-in as simple as possible e.g <player url:fileRef />
Each instance on the page would then be injected with the ~20 lines of html and the unique fileRef at display time.
What's the best way to do this? Javascript? jquery?
I read that 'document.write' - isnt the answer.
Is document.getElementByClassName("player").innerHTML = "html goes here"; the best way>
I read that .getELementByClassName("player") replaces ALL instances of the class - so how best inject the unique fileRef into each instance.
Your guidance would be most welcome
PS We already have the (html5) mp3 music player, but the html for each instance is ~20 lines.
It's fine if you want to add the class 'player' to each of your elements, but your selector should not be document.getElementsByClassName("player") as that will not differentiate any of the elements from one another. Ideally, you'd have a list of IDs that you could loop over and use to select your individual elements. Something like this could work:
var ids = ["id1", "id2", "id3", "id4"];
var i;
for(i = 0; i < ids.length; i++) {
var player = document.getElementById(ids[i]);
player.innerHTML = getHtmlForPlayer(id[i]); // you'll need to build this
}
Note, I have written this without knowing too much about how your HTML is generated, but it should get you started.
using jQuery you can use something like
$("player").each(function(){
var id = $(this);
var url = $(this).prop("url");
$.get(url, function(data){
$(id).html(data);
}
}
Note that this is untested, i would go for span/div with a class="player", and the use of data-url tags.
<span class="player" data-url="url"></span>
then change the $("player") to $(".player")
I have a simple spotify (JS) app and I want to either:
Create a new playlist with the first track and play it, or
Append the song to the currently playing playlist
var playlist = new models.Playlist();
...
var search = new models.Search(query);
search.observe(models.EVENT.CHANGE, function() {
if (search.tracks.length) {
var track = search.tracks[0];
playlist.add(track);
if (!models.player.playing)
models.player.play(track, playlist);
}
});
search.appendNext();
Shouldn't this simply append the new track to the existing playlist? (playlist is scoped higher up, and it is definitely not re-created each time).
Currently nothing happens when a track is playing.
it actually does work, but the playlist view takes a long time to render as updated. (although it immediately appends the track - as can be seen by clicking next). as mentioned in the comments however, it doesn't play a playlist when created with a name, only ones that are temporary and thus have a URI.
Trying to play a sound via javascript and want to change it dynamically using sessionstorage
The following is a simplified version that plays sound/s in android/FF Linux/Win when u click the "sprite me button" - I've included other buttons for the example to set and retrieve session values in HTML5.
http://globability.org/webapp/asprite20111124_8.html
The wiki below has Android phone specs where it works: (Samsung Galaxy SII) in case you're interested
http://globability.org/wiki/doku.php?id=current_working_specs_p-tab / also see http://globability.org/wiki/doku.php?id=mobile_pointing_tablet to get a proper idea about what it is that I am working on.
What I need is to have the "play soundsprite" javascript that you can see below in the following section load from sessionstorage and insert values loaded from sessionstorage inserted into an array.
I am not looking for any changes is how the sound is played back - just need to make a dynamically built array work from within the particular javascript.
The code below is based on the soundsprite idea from www.phpied.com/audio-sprites/ by Stoyan Stefanov - made to reduce the http calls needed for playing sounds... Also stabilizes the sound quality, less choppy sound etc.
Antway here goes: YOU ONLY NEED TO LOOK AT PSEUDOCODE SECTION - The rest is functioning
<script>
var thing = 'the thing';
function shut() {
if (typeof thing.pause !== 'undefined') {
thing.pause();
}
}
function log(what) {
document.getElementById('log').innerHTML += what + "<br>";
}
var spriteme = function(){
var sprites = {
// id: [start, length]
'blank':[0.1, 0.5], //This the first sprite, it has to be the first defined and is
called first, it is a blank piece of sound in the combined sound file and needed as of
now.
'success':[13, 2,5],
/* I would like to be able to set the parameters i.e. sound bite to play dynamically -
here a pseudocode example using session storage in preparation for getting the sound
parameters from a database
'wordgen'[null,null];
//this array should be dynamically built from values read from the two session storage keys not sure you need the new thing in HTML5
sessionStorage.globabilitykey1;
sessionStorage.globabilitykey2;
strkey1=globabilitykey1
strkey2=globabilitykey2
var gkey1=parsefloat(strkey1)
var gkey2=parsefloat(strkey2)
'wordgen':[gkey1,gkey2]
and then the idea is to replace the array success in the script with the "generated"
array 'wordgen' to allow dynamic seting of sound to play back */
//the following are sound bites from the collection of soundsprites the site plays from
'word1': [0.5, 2,36], //one
'word2': [3.1, 3.0], //two
'word3': [7.0, 1.82], //three
'word4': [10.03, 2], //four ?
},
song = ['blank', 'success'],
//here you're setting the playback sequence (this is where I would like to replace 'success' with 'wordgen'
current = 0,
id = song[current],
start = 0,
end = sprites[id][1],
int;
thing = document.getElementById('sprite');
thing.play();
log('file: ' + thing.currentSrc);
log(id + ': start: ' + sprites[id].join(', length: '));
// change
int = setInterval(function() {
if (thing.currentTime > end) {
thing.pause();
if (current === song.length - 1) {
clearInterval(int);
return;
}
current++;
id = song[current];
start = sprites[id][0];
end = start + sprites[id][1]
thing.currentTime = start;
thing.play();
log(id + ': start: ' + sprites[id].join(', length: '));
}
}, 10);
};
</script>
Any ideas on how to dynamically create the 'wordgen' array within the javascript based on the values is sessionstorage ?
The whole code/working example can be seen here: http://globability.org/webapp/asprite20111124_8.html
I know the page is one ugly mess... but this is an alpha prototype :)
Argh... it was sooooo simple, I was this close to solving it myself, however a kind freelancer at freelancer.com who has perviously helped me getting one of my ideas to work did it again this time...
And without further ado:
'wordgen':eval("["+sessionStorage.globabilitykey1+","+sessionStorage.globabilitykey2+"]"),
That was what I needed - I had been trying to do the same but without the " eval " in front and the paranthesis around the arguments....
Oh and don't forget to set the value by clicking the button before you try or there will be no sound coming out of your speakers ;)
Here's a link to the working page if you care to try and if you want to see the code in it's "entirity": http://globability.org/webapp/asprite20111201_2.html - Thanks to those of you voting the question up!!!