With SoundCloud default audio widget can you set it to shuffle from JavaScript? - soundcloud

I am using the default SoundCloud audio widget to play a playlist and would like the songs to play in a random order. I am using the SoundCloud JavaScript SDK oEmbed object (the iframe version).
<script src="//connect.soundcloud.com/sdk.js"></script>
SC.oEmbed("//soundcloud.com/buzzinfly", {
auto_play: true,
start_track: random_start_track,
iframe: true,
maxwidth: 480,
enable_api: true,
randomize: true
},
document.getElementById("soundcloud_player"));
The documentation doesn't mention any randomize or shuffle attribute, but other undocumented attributes like start_track are supported with oembed.
//developers.soundcloud.com/docs/oembed#parameters
//developers.soundcloud.com/docs/widget
Does any one know if there is an attribute to shuffle the song order in the default audio widget?
If not, is there is a way to capture events from the default SoundCloud widget, so I can play a random song after a song finish event? Like you can with the custom player.
//developers.soundcloud.com/docs/custom-player#events
$(document).bind('onPlayerTrackSwitch.scPlayer', function(event, track){
// goto random track
});
Thanks
Update
Here is code am I using after getting gryzzly's help.
The next button works great. I can skip thru random songs. Unfortunately, when a track finishes playing, I hear the audio of two songs: the next song in the playlist and the random track that is skipped to in the FINISH event.
var widget = null;
var song_indexes = new Array();
var current_index = 0;
$(function() {
var iframe = document.querySelector('#soundcloud_player iframe');
widget = SC.Widget(iframe);
widget.bind(SC.Widget.Events.READY, function() {
widget.unbind(SC.Widget.Events.FINISH);
widget.bind(SC.Widget.Events.FINISH, function() {
play_next_shuffled_song();
});
widget.getSounds(function(sounds) {
create_shuffled_indexes(sounds.length);
play_next_shuffled_song();
});
});
$("#button_sc_next").on("click", play_next_shuffled_song);
});
function play_next_shuffled_song() {
current_index++;
if (current_index >= song_indexes.length) {
current_index = 0;
}
var track_number = song_indexes[current_index];
widget.skip(track_number);
}
function create_shuffled_indexes (num_songs) {
for (var i=0;i<num_songs;i++) {
song_indexes.push(i);
}
song_indexes = shuffle(song_indexes);
}
//+ Jonas Raoni Soares Silva
//# http://jsfromhell.com/array/shuffle [v1.0]
function shuffle(o){ //v1.0
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
return o;
};

You can find the documentation for Widget API here.
Pseudo code for this would be something like:
load widget
get widget sounds LENGTH /* say 5 */
create an ARRAY of that LENGTH and shuffle it /* to have something like [3, 1, 0, 2, 4] */
create function SKIP that will check if ARRAY still has any items in it (length is not 0) and skip player to array.pop() index /* pop will return the last item in the array and return its value */
attach event handler to FINISH events that will run SKIP function
run SKIP function first time yourself
If user skips with the “next” control in widget's UI the widget will
just play next song in the list. You could perhaps detect that
somehow and skip to another random track.
I hope this helps!

Related

RxJs Observable with infinite scroll OR how to combine Observables

I have a table which uses infinite scroll to load more results and append them, when the user reaches the bottom of the page.
At the moment I have the following code:
var currentPage = 0;
var tableContent = Rx.Observable.empty();
function getHTTPDataPageObservable(pageNumber) {
return Rx.Observable.fromPromise($http(...));
}
function init() {
reset();
}
function reset() {
currentPage = 0;
tableContent = Rx.Observable.empty();
appendNextPage();
}
function appendNextPage() {
if(currentPage == 0) {
tableContent = getHTTPDataPageObservable(++currentPage)
.map(function(page) { return page.content; });
} else {
tableContent = tableContent.combineLatest(
getHTTPDataPageObservable(++currentPage)
.map(function(page) { return page.content; }),
function(o1, o2) {
return o1.concat(o2);
}
)
}
}
There's one major problem:
Everytime appendNextPage is called, I get a completely new Observable which then triggers all prior HTTP calls again and again.
A minor problem is, that this code is ugly and it looks like it's too much for such a simple use case.
Questions:
How to solve this problem in a nice way?
Is is possible to combine those Observables in a different way, without triggering the whole stack again and again?
You didn't include it but I'll assume that you have some way of detecting when the user reaches the bottom of the page. An event that you can use to trigger new loads. For the sake of this answer I'll say that you have defined it somewhere as:
const nextPage = fromEvent(page, 'nextpage');
What you really want to be doing is trying to map this to a stream of one directional flow rather than sort of using the stream as a mutable object. Thus:
const pageStream = nextPage.pipe(
//Always trigger the first page to load
startWith(0),
//Load these pages asynchronously, but keep them in order
concatMap(
(_, pageNum) => from($http(...)).pipe(pluck('content'))
),
//One option of how to join the pages together
scan((pages, p) => ([...pages, p]), [])
)
;
If you need reset functionality I would suggest that you also consider wrapping that whole stream to trigger the reset.
resetPages.pipe(
// Used for the "first" reset when the page first loads
startWith(0),
//Anytime there is a reset, restart the internal stream.
switchMapTo(
nextPage.pipe(
startWith(0),
concatMap(
(_, pageNum) => from($http(...)).pipe(pluck('content'))
),
scan((pages, p) => ([...pages, p]), [])
)
).subscribe(x => /*Render page content*/);
As you can see, by refactoring to nest the logic into streams we can remove the global state that was floating around before
You can use Subject and separate the problem you are solving into 2 observables. One is for scrolling events , and the other is for retrieving data. For example:
let scrollingSubject = new Rx.Subject();
let dataSubject = new Rx.Subject();
//store the data that has been received back from server to check if a page has been
// received previously
let dataList = [];
scrollingSubject.subscribe(function(page) {
dataSubject.onNext({
pageNumber: page,
pageData: [page + 10] // the data from the server
});
});
dataSubject.subscribe(function(data) {
console.log('Received data for page ' + data.pageNumber);
dataList.push(data);
});
//scroll to page 1
scrollingSubject.onNext(1);
//scroll to page 2
scrollingSubject.onNext(2);
//scroll to page 3
scrollingSubject.onNext(3);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.js"></script>

How to get dynamic element HTML use Addon SDK with Timers?

I want to scrape a page, the HTML content of this page auto change in a time frame. So i want to use pageMod and Timers of Addon Sdk to get the element innerHtml which change often.
Here are my scripts :
In main.js :
var tag = "container1";
var data = require("sdk/self").data;
var pageMod = require("sdk/page-mod");
var timer = require("sdk/timers");
var i = 0;
function scrapeData()
{
i = i + 1;
console.log("Begin pageMod : " + i);
pageMod.PageMod({
include: "*.test.com",
contentScriptFile: data.url("element-getter.js"),
contentScriptWhen: 'ready',
onAttach: function(worker) {
worker.port.emit("getElements", tag);
worker.port.on("gotElement", function(elementContent) {
console.log(elementContent);
});
}
});
console.log("End pageMod : " + i);
}
timer.setInterval(scrapeData, 10000);
And in data/element-getter.js :
self.port.on("getElements", function(tag) {
var elements = document.getElementById(tag);
self.port.emit("gotElement", elements.innerHTML);
});
After install this Firefox Add-on, when timers is running, it can only get the innerHtml one time, and the other time, it only display Begin pageMod and End pageMode in console log. Please help.
What you're currently doing is attaching the same page mod multiple times.
What you should do is move the timer inside the content script:
data/element-getter.js:
function scrapeData() {
var elements = document.getElementById(tag);
self.port.emit("gotElement", elements.innerHTML);
}
setInterval(scrapeData, 10000);
If you really want to keep the timer in the main page, then you need to maintain an array of worker instances, and loop through this array to emit your custom event. See this answer for more details.
(PS. Depending on your use case, the sdk/frame/hidden-frame module might be of interest.)

Why aren't these two functions toggling on click event?

I'm trying to toggle two functions. When user clicks the pause button, the input fields are disabled, the label is text is changed to grey and the button changes to a different image. I thought I could use .toggle(), but I can't get the two functions to work either -- only the first one function runs (pauseEmailChannel();), not both on toggle click. I found the even/odd clicks detection script here on SO, but that is not "toggling" these two functions on the click event. My code may be ugly code, but I'm still learning and wanted to show how I am thinking -- right or wrong. At any rate, can someone give me a solution to how to do this? I didn't think it would be too difficult but I'm stuck. Thanks.
HTML
jQuery
$(".btn_pause").click(function(){
var count = 0;
count++;
//even odd click detect
var isEven = function(num) {
return (num % 2 === 0) ? true : false;
};
// on odd clicks do this
if (isEven(count) === false) {
pauseEmailChannel();
}
// on even clicks do this
else if (isEven(count) === true) {
restoreEmailChannel();
}
});
// when user clicks pause button - gray out/disable
function pauseEmailChannel(){
$("#channel-email").css("color", "#b1b1b1");
$("#notify-via-email").attr("disabled", true);
$("#pause-email").removeClass("btn_pause").addClass("btn_disable-pause");
}
// when user clicks cancel button - restore default
function restoreEmailChannel(){
$("#channel-email").css("color", "#000000");
$("#notify-email").attr("disabled", false);
$("#pause-email").removeClass("disable-pause").addClass("btn_pause");
$("input[value='email']").removeClass("btn_disable-remove").addClass("btn_remove");
}
try this code. It should work fine, except that I could make a mistake when it is even and when odd, but that should be easy to fix.
$(".btn_pause").click(function(){
var oddClick = $(this).data("oddClick");
$(this).data("oddClick", !oddClick);
if(oddClick) {
pauseEmailChannel();
}
else {
restoreEmailChannel();
}
});
The count variable is initialized and set to 0 every time .btn_pause is clicked. You need to move the variable to a higher scope.
For example,
$(".btn_pause").each(function(){
var count = 0;
$(this).click(function(){
count++;
...
});
});
In this way count is initialized only once and it is accessible in the click event handler.
As an alternative way you can also use:
$(".btn_pause").each(function(){
var count = 0;
$(this).click(function(){
[restoreEmailChannel, pauseEmailChannel][count = 1 - count]();
});
});
If the previous construct was too abstract, a more verbose one will look like this:
$(".btn_pause").each(function(){
/* Current element in the array to be executed */
var count = 0;
/* An array with references to Functions */
var fn = [pauseEmailChannel, restoreEmailChannel];
$(this).click(function(){
/* Get Function from the array and execute it */
fn[count]();
/* Calculate next array element to be executed.
* Notice this expression will make "count" loop between the values 0 and 1.
*/
count = 1 - count;
});
});

Drag an event in fullCalendar component with a specific duration

I've seen the solution to drag and drop external events in fullcalendar. But, in this demo, all the external events have a duration of 2 hours (because defaultEventMinutes parameter is set to 120). I'm trying to change this demo in order to manage events with different durations. Say, "My event 1" is 45min long, "My event 2" is 165min, etc.
At the beginning I though there may be an attribute to store the duration in the eventObject, but according to the documentation, it's not the case.
Then, I thought it would be possible to change the value of 'defaultEventMinutes' when starting dragging the event. But apparently, I can't do it without rebuilding the whole calendar.
According to you, what is the best means to meet this requirement?
Thanks in advance for your advice...
Worked on this as well and have solved the duration shown on fullCalendar this way:
Having a custom "setOptions" function for fullCalendar.
Having a property for fullCalendar called "dragMinutes" that can be set during elements $(this).draggable({start:...}).
Here is the code for the custom setOptions:
...
function Calendar(element, options, eventSources) {
var t = this;
// hack for setting options that updates
function setOptions(new_options, refresh) {
$.extend(options, new_options);
if (refresh) {
var viewName = currentView.name;
changeView(viewName, true);
}
}
// exports ...
t.setOptions = setOptions;
...
Heres the code for handling "dragMinutes" option in fullCalendar:
/* External Dragging
--------------------------------------------------------------------------------*/
function dragStart(_dragElement, ev, ui) {
hoverListener.start(function (cell) {
clearOverlays();
if (cell) {
if (cellIsAllDay(cell)) {
renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
} else {
var d1 = cellDate(cell);
if (opt('dragMinutes'))
var d2 = addMinutes(cloneDate(d1), opt('dragMinutes'));
else
var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes'));
renderSlotOverlay(d1, d2);
}
}
}, ev);
}
And heres how i make event draggable and update the "dragMinutes":
// make the event draggable using jQuery UI
$(this).draggable({
containment: 'document',
// return a custom styled elemnt being dragged
helper: function (event) {
return $('<div class="uv-planning-dragging"></div>').html($(this).html());
},
opacity: 0.70,
zIndex: 10000,
appendTo: 'body',
cursor: 'move',
revertDuration: 0,
revert: true,
start: function (e, ui) {
// set the "dragMinutes" option in fullCalendar so shown interval about to be added is correct.
var data = $(this).data('eventObject');
if (data) {
var min = data.jsonProps.durationMsec / 1000 / 60;
if (macroCalendar.calendar) {
macroCalendar.calendar.fullCalendar('setOptions', { dragMinutes: Math.round(min) }, false);
}
}
},
stop: function (e, ui) {
// further process
}
});
Hope it helps.
If anyone still visits the thread and don't find the solution, the solution would be to set the duration parameter in event div... and then call draggable on that div.
$(this).data('event', {
title: 'new event title', // use the element's text as the event title
id: $(this).attr('id'),
stick: true, // maintain when user navigates (see docs on the renderEvent method)
duration: '03:00:00' // will set the duration during drag of event
});
Currently, the best solution I have found is adding a duration attribute on my event Object, then the code to create my fullCalendar looks like this:
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
editable: true,
droppable: true, // this allows things to be dropped onto the calendar !!!
drop: function(date, allDay) { // this function is called when something is dropped
// retrieve the dropped element's stored Event Object
var originalEventObject = $(this).data('eventObject');
// we need to copy it, so that multiple events don't have a reference to the same object
var copiedEventObject = $.extend({}, originalEventObject);
// assign it the date that was reported
copiedEventObject.start = date;
// HERE I force the end date based on the start date + duration
copiedEventObject.end = new Date(date.getTime() + copiedEventObject.duration * 60 * 1000);
copiedEventObject.allDay = allDay;
// render the event on the calendar
// the last `true` argument determines if the event "sticks" (http://arshaw.com/fullcalendar/docs/event_rendering/renderEvent/)
$('#calendar').fullCalendar('renderEvent', copiedEventObject, true);
// is the "remove after drop" checkbox checked?
if ($('#drop-remove').is(':checked')) {
// if so, remove the element from the "Draggable Events" list
$(this).remove();
}
}
});
The only drawback is when you're dragging the event, the event duration looks like defaultEventMinutes and not the actual duration, but I don't know how to fix it
These special properties can either be specified in the provided event object, or they can be standalone data attributes:
<!-- DURATION OF 3 hours EVENT WILL PROPAGATE TO CALENDAR WHEN DROPPED -->
<div class='draggable' data-event='1' data-duration='03:00' />
https://fullcalendar.io/docs/dropping/eventReceive/
With the latest fullcalendar v2.0.2, if you want the overlay to be of the particular duration, you can update in this function of fullcalendar-arshaw.js
function dragStart(_dragElement, ev, ui) {
hoverListener.start(function(cell) {
clearOverlays();
if (cell) {
var seconds = duration_in_minutes * 1000 * 60 ;
// we need to pass seconds into milli-seconds
if (d1.hasTime()) {
d2.add(seconds);
renderSlotOverlay(d1, d2, cell.col);
}
else {
d2.add(calendar.defaultAllDayEventDuration);
renderDayOverlay(d1, d2, true, cell.col);
}
}
}, ev);
}
Here, pass your duration in the external events object and that object you can fetch in _dragElement and then convert it into milli-seconds and pass it in d2.add(seconds). This will create the shadow of that mili-seconds on that calendar.
For non-external events you can use the fullcalendar settings:
defaultTimedEventDuration: (hours+':00:00'),
forceEventDuration: true,
// defaultEventMinutes: hours*60, // not needed
and in the event data you do not set the end property (or you null it):
eventData = {
title: title,
start: start,
// end: end, // MUST HAVE no end for fixedduration
color: '#00AA00',
editable: true, // for dragging
};
Ref: http://fullcalendar.io/docs/event_data/defaultTimedEventDuration/
Tip: In case you want to prevent the resizing of the events which is possible due to editable: true, you can use CSS to hide the handle: .fc-resizer.fc-end-resizer { display:none; }
Since v4 some of the above options are not working at all. The problem i was facing was as follows:
All day items for me have a duration, but not a start time. When i select a start time by dragging, the start time is set but as soon as i set the end date ( which is done similar as above answers ), the end date is reset again.. there is something buggy going on in the setDate function... the end date is set, this part works, then it does a comparisson on itself to find out the time difference between the dates, but the date is already set by the system itself causing the difference to be 0 which is causing the enddate to be set to null again......
A giant pain in my neck i got to say... it works perfect when staying within the timeline, but that's about it.
I managed to 'fix', more like destroy it by using this line in the eventDrop event, but it will also work in any other events you may use:
update your event with ajax here, since you have the start and end date *
calendar.refetchEvents(); in the success function
This is going to refetch all the events, it sounds pretty killer for performance but it doesn't seem to take up much time, try it for yourself.
This way my titles, times etc are always up to date and the calendar is showing the right end date.

What is the proper way in OpenLayers (OSM) to trigger a popup for a feature?

I have the feature ID, I can grab the marker layer on GeoRSS loadend, but I'm still not sure how to cause the popup to appear programmatically.
I'll create the popup on demand if that's necessary, but it seems as though I should be able to get the id of the marker as drawn on the map and call some event on that. I've tried using jQuery and calling the $(marker-id).click() event on the map elements, but that doesn't seem to be working. What am I missing?
Since I was asked for code, and since I presumed it to be boilerplate, here's where I am so far:
map = new OpenLayers.Map('myMap');
map.addLayer(new OpenLayers.Layer.OSM());
map.addLayer(new OpenLayers.Layer.GeoRSS(name,url));
//I've done some stuff as well in re: projections and centering and
//setting extents, but those really don't pertain to this question.
Elsewhere I've done a bit of jQuery templating and built me a nice list of all the points that are being shown on the map. I know how to do a callback from the layer loadend and get the layer object, I know how to retrieve my layer out of the map manually, I know how to iter over the layers collection and find my layer. So I can grab any of those details about the popup, but I still don't know how to go about using the built-in methods of the DOM or of this API to make it as easy as element.click() which is what I would prefer to do.
You don't have to click the feature to open a popup.
First you need a reference to the feature from the feature id. I would do that in the loadend event of the GeoRSS layer, using the markers property on the layer.
Assuming you have a reference to your feature, I would write a method which handles the automatic popup:
var popups = {}; // to be able to handle them later
function addPopup(feature) {
var text = getHtmlContent(feature); // handle the content in a separate function.
var popupId = evt.xy.x + "," + evt.xy.y;
var popup = popups[popupId];
if (!popup || !popup.map) {
popup = new OpenLayers.Popup.Anchored(
popupId,
feature.lonlat,
null,
" ",
null,
true,
function(evt) {
delete popups[this.id];
this.hide();
OpenLayers.Event.stop(evt);
}
);
popup.autoSize = true;
popup.useInlineStyles = false;
popups[popupId] = popup;
feature.layer.map.addPopup(popup, true);
}
popup.setContentHTML(popup.contentHTML + text);
popup.show();
}
fwiw I finally came back to this and did something entirely different, but his answer was a good one.
//I have a list of boxes that contain the information on the map (think google maps)
$('.paginatedItem').live('mouseenter', onFeatureSelected).live('mouseleave',onFeatureUnselected);
function onFeatureSelected(event) {
// I stuff the lookup attribute (I'm lazy) into a global
// a global, because there can be only one
hoveredItem = $(this).attr('lookup');
/* Do something here to indicate the onhover */
// find the layer pagination id
var feature = findFeatureById(hoveredItem);
if (feature) {
// use the pagination id to find the event, and then trigger the click for that event to show the popup
// also, pass a null event, since we don't necessarily have one.
feature.marker.events.listeners.click[0].func.call(feature, event)
}
}
function onFeatureUnselected(event) {
/* Do something here to indicate the onhover */
// find the layer pagination id
var feature = findFeatureById(hoveredItem);
if (feature) {
// use the pagination id to find the event, and then trigger the click for that event to show the popup
// also, pass a null event, since we don't necessarily have one.
feature.marker.events.listeners.click[0].func.call(feature, event)
}
/* Do something here to stop the indication of the onhover */
hoveredItem = null;
}
function findFeatureById(featureId) {
for (var key in map.layers) {
var layer = map.layers[key];
if (layer.hasOwnProperty('features')) {
for (var key1 in layer.features) {
var feature = layer.features[key1];
if (feature.hasOwnProperty('id') && feature.id == featureId) {
return feature;
}
}
}
}
return null;
}
also note that I keep map as a global so I don't have to reacquire it everytime I want to use it