Chromecast custom CAF receiver - javascript

I'm having issues with the new CAF receiver not registering the YouTube iframe player as a player, and that it is playing. After 5 minutes connected to the receiver, the connection is broken because it thinks that the player is idle.
This is the sender-code
var metadata = new chrome.cast.media.GenericMediaMetadata();
metadata.title = "Foo - Bar";
metadata.image = 'https://img.youtube.com/vi/IXNrHusLXoM/mqdefault.jpg';
metadata.images = ['https://img.youtube.com/vi/IXNrHusLXoM/mqdefault.jpg'];
var mediaInfo = new chrome.cast.media.MediaInfo();
mediaInfo.contentType = "video/*";
mediaInfo.contentId ="IXNrHusLXoM";
mediaInfo.duration = 300;
var request = new chrome.cast.media.LoadRequest();
request.media = mediaInfo;
request.customData = customData;
request.metadata = metadata;
castSession.loadMedia(request).then(
function() {
console.log('Load succeed');
},
function(errorCode) {
console.log('Error code: ' + errorCode);
});
The receiver code can be found here: https://github.com/zoff-music/zoff-chromecast-receiver/blob/feature/v3/receiver.js
Is there any way of having the new CAF receiver hook into the YouTube iframe player, or "manually" dispatch LOADED, BUFFERING, PLAYING, PAUSED, STOPPED events so that the receiver doesn't disconnect from the sender?
Edit: with the above code, the PlayerState gets to the BUFFERING stage, but stops there. The promise with the log "Load succeed" is never triggered.

I managed to trick the receiver with fake mediaEelement. You can see the code in pastebin
const context = cast.framework.CastReceiverContext.getInstance();
const playerManager = context.getPlayerManager();
var yt_events = {};
var pause_request = false;
var yt_player;
var yt_video_fake = {
removeAttribute: function(attr) {
},
setAttribute: function(attr, val) {
},
getCurrentTimeSec: function() { return yt_player && yt_player.getCurrentTime ? yt_player.getCurrentTime() : 0; },
getDurationSec: function() { return yt_player ? yt_player.getDuration() : 0; },
getVolume: function() {
if(!yt_player || !yt_player.getVolume) {
return 0;
}
var volume = new cast.framework.messages.Volume();
volume.level = yt_player.getVolume() / 100;
volume.muted = yt_player.isMuted() ? true : false;
return volume;
},
setVolume: function(vol) { yt_player && yt_player.setVolume(vol.level * 100); },
getState: function() {
if(!yt_player || !yt_player.getPlayerState) {
return 'IDLE';
}
var state = yt_player.getPlayerState();
var _state;
if(pause_request) {
pause_request = false;
state = YT.PlayerState.PAUSED;
}
switch(state) {
default: case YT.PlayerState.UNSTARTED:
_state = 'IDLE';
break;
case YT.PlayerState.PLAYING:
_state = 'PLAYING';
break;
case YT.PlayerState.PAUSED:
_state = 'PAUSED';
break;
case YT.PlayerState.BUFFERING:
_state = 'BUFFERING';
break;
case YT.PlayerState.ENDED:
_state = 'ENDED';
break;
}
return _state;
},
addEventListener: function(e, func) { },
load: function() {},
play: function() { yt_player && yt_player.playVideo(); },
pause: function() { if(yt_player && yt_player.pauseVideo) {pause_request = true; yt_player.pauseVideo(); }},
seek: function(timeTo) { yt_player && yt_player.seekTo(timeTo, true);},
reset: function() {
if(yt_player) {
try { yt_player.destroy && yt_player.destroy(); } catch(e) {
//console.trace(e);
};
delete yt_player;
}
},
registerErrorCallback: function(func) { yt_events['error'] = func; },
registerEndedCallback: function(func) { yt_events['ended'] = func; },
registerLoadCallback: function(func) { yt_events['load'] = func; },
unregisterErrorCallback: function () { delete yt_events['error'] },
unregisterEndedCallback: function () { delete yt_events['ended']},
unregisterLoadCallback: function () { delete yt_events['load']}
};
Object.defineProperty(yt_video_fake, 'currentTime', {
val1: null,
get: function() { return yt_player && yt_player.getCurrentTime ? yt_player.getCurrentTime() : this.val1; },
set: function(newValue) {
yt_player && yt_player.seekTo(newValue, true);
this.val1 = newValue;
},
enumerable : true,
configurable : true
});
Object.defineProperty(yt_video_fake, 'volume', {
val1: null,
get: function() { var vol = this.getVolume(); if(vol) return vol['level']; return 1; },
set: function(newValue) {
yt_player && yt_player.setVolume && yt_player.setVolume(newValue * 100);
this.val1 = newValue;
},
enumerable : true,
configurable : true
});
Object.defineProperty(yt_video_fake, 'duration', {
val1: null,
get: function() { return this.getDurationSec(); },
set: function() {},
enumerable : true,
configurable : true
});
function YoutubePlayMedia(videoid) {
var yt_container = $('#yt_container');
if(!yt_container.length) {
yt_container = $('<div id="yt_container" style="position:absolute;top:0;left:0;width:100%;height:100%;"></div>');
$('body').append(yt_container);
}
yt_container.html('<iframe id="youtube_container" style="width:100%;height:100%;" frameborder="0" allowfullscreen="1" allow="autoplay; encrypted-media" title="YouTube video player" src="//www.youtube.com/embed/' + videoid +'?autoplay=1&enablejsapi=1&modestbranding=1&controls=0&fs=0&iv_load_policy=3&rel=0&cc_load_policy=1&cc_lang_pref=bg"></iframe>');
yt_player = new YT.Player('youtube_container', {
events: {
'onReady': function(e) {
yt_player.is_loaded = true;
yt_player.playVideo();
},
'onStateChange': function(e) {
switch(e.data) {
case YT.PlayerState.PLAYING:
if(yt_player.is_loaded) {
if(yt_events['load']) {
yt_events['load']();
}
}
break;
case YT.PlayerState.ENDED:
//yt_events['ended'] && yt_events['ended'](e);
break;
}
},
'onError': function(e) {
//yt_events['error'] && yt_events['error'](e);
}
}
});
}
function YoutubeLoadMedia(url) {
current_media_type = 'Youtube';
window.onYouTubeIframeAPIReady = function() {
window.youtube_loaded = true;
YoutubePlayMedia(url);
}
if(!window.youtube_script) {
window.youtube_script = document.createElement('script');
window.youtube_script.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(window.youtube_script, firstScriptTag);
} else {
// Вече имаме api направо действаме
YoutubePlayMedia(url);
}
}
playerManager.setMessageInterceptor(
cast.framework.messages.MessageType.LOAD,
loadRequestData => {
if (loadRequestData.media && loadRequestData.media.contentId) {
YoutubeLoadMedia(loadRequestData.media.contentId);
playerManager.setMediaElement(yt_video_fake);
return false;
}
return loadRequestData;
}
);
const options = new cast.framework.CastReceiverOptions();
options.disableIdleTimeout = true;
context.start(options);

The YouTube iframe player is not designed to be a player for a Cast receiver.

Related

Javascript help needed, store local variable

As Javascript newbie I'm not able to store locally a variable and reuse from Google cache.
I have a counter that starts onClick from zero then increments until a specific number then goes to a new page.
In the new page I need to continue the counter from the previous number.
I can't use cookies since this is for a Chrome extension, so I used storage.local and I set my manifest as follow:
...
"permissions": ["storage", "https://example.com/*", "unlimitedStorage"],
"content_scripts": [
{
"matches": ["https://example.com/*"],
"js": ["js/jquery-2.1.4.min.js", "js/example.js"],
"css": ["css/example.css"]
}
],
...
So basically when I click my button it starts the follow function and it increments this.count
function Button(t, e, i) {
var n = this;
n.$el = t,
n.count = (Record.get(function() {tmf.followBtn.counter()}) > 1 ? Record.get(function() {tmf.followBtn.counter()}) : 0),
n.$title = t.find(".tmf-btn__title"),
n.$subtitle = t.find(".tmf-btn__subtitle"),
n.action = new BulkAction(e, i), n.$el.on("click", function() {
n.$el.addClass("tmf-btn--active"),
n.$title.text(n.count),
n.action.paused ? (n.action.proceed(), n.$subtitle.text("Click to pause")) : (n.action.pause(), n.$subtitle.text("Click to continue"))
})
}
function Profile(t) {
this.$el = $(".ProfileCard:eq(" + t + ")"),
this.isLoaded() && (this.$btn = this.$el.find(".user-actions-follow-button"),
this.id = this.$el.data("user-id") + "-")
}
function BulkAction(t, e) {
this.callback = t,
this.interval = e,
this.paused = !0
}
function gotoPage() {
window.location.replace("https://example.com/newpage/");
}
$.fn.isPresent = function() {
return this.length > 0
};
var USER_ID = $("#user-dropdown").find("[data-user-id]").data("user-id"),
Record = {
storage: chrome.storage.local,
key: USER_ID.toString(),
get: function(t) {
var e = this;
this.storage.get(this.key, function(i) {
e.result = i, t.call()
})
},
set: function() {
this.storage.set(this.result)
},
add: function(t) {
this.result[this.key] ? this.result[this.key] += t : this.result[this.key] = t, this.set()
},
includes: function(t) {
return this.result[this.key] ? this.result[this.key].includes(t) : !1
}
},
tmf = {
init: function() {
var t = this;
t.$el = $("<div>").addClass("tmf animated").appendTo("body")
},
toggle: function() {
var t = this;
this.$el.removeClass("flipInY flipOutY tmf--active"), $(".ProfileCard").length > 10 ? t.$el.load(chrome.extension.getURL("html/actions.html"), {}, function() {
$(".tmf-btn").on("click", function() {
t.$el.addClass("tmf--active")
}), t.followBtn = new Button($(".tmf-btn--follow"), function() {
this.follow()
}, 100), t.unfollowBtn = new Button($(".tmf-btn--unfollow"), function() {
this.unfollow()
}, 100), t.$el.addClass("flipInY");
var e = new MutationObserver(function(e) {
e.forEach(function(e) {
var i = $("#message-drawer").html();
i.includes("66885") && t.followBtn.setIdle()
})
});
e.observe(document.getElementById("message-drawer"), {
subtree: !0,
characterData: !0,
childList: !0
}), $("#tmf_without_exception").on("change", function() {
t.withoutException = this.checked,
this.checked ? (t.followBtn.$subtitle.text("without exception"),t.unfollowBtn.$subtitle.text("without exception")) : (t.followBtn.$subtitle.text("who have never been unfollowed"), t.unfollowBtn.$subtitle.text("who do not follow you"))
})
}) : this.$el.addClass("flipOutY")
},
withoutException: !1
};
$.extend(Button.prototype, {
incrementCount: function() {
this.count++,
this.$title.text(this.count)
},
counter: function() {
return this.count
},
setIdle: function() {
var t = this;
if (!t.action.paused) {
t.action.pause();
}
}
}),
$.extend(Profile.prototype, {
isLoaded: function() {
return this.$el.isPresent()
},
isFollowing: function() {
return this.$el.find(".FollowStatus").isPresent()
},
isProtected: function() {
return this.$el.find(".Icon--protected").isPresent()
},
isNotFollowing: function() {
return !this.isFollowing()
},
isFollowable: function() {
return this.$btn.children(".follow-text").is(":visible")
},
isFollowed: function() {
return this.$btn.children(".following-text").is(":visible")
},
follow: function() {
if (tmf.followBtn.counter() > 1) {
tmf.followBtn.action.pause();
Record.set(function() {tmf.followBtn.counter()});
gotoPage();
};
this.isFollowable() && !this.isProtected() && tmf.followBtn.counter() < 6 && (tmf.withoutException || !Record.includes(this.id) ? (this.click(), tmf.followBtn.incrementCount()) : this.log("warn", "Already unfollowed once"))
},
unfollow: function() {
this.isFollowed() && (tmf.withoutException || this.isNotFollowing()) && (this.click(), tmf.unfollowBtn.incrementCount(), Record.add(this.id))
},
click: function() {
this.log("success", 'Clicked on "' + this.$btn.children(":visible").text().trim() + '"'), this.$btn.click(), this.clicked = !0
},
log: function(t, e) {
$("<div>").addClass("tmf-log").addClass("tmf-log--" + t).text(e).insertBefore(this.$el.find(".ProfileCard-bio"))
}
}),
$.extend(BulkAction.prototype, {
nth: 0,
loadAttempts: 0,
_loadProfiles: function() {
this.loadAttempts < 10 && (this.last.$btn.focus(), this.loadAttempts++, this._sleep(700))
},
_sleep: function(t) {
var e = this;
setTimeout(function() {
e.run()
}, t)
},
run: function() {
if (!this.paused) {
var t = new Profile(this.nth);
t.isLoaded() ? (this.loadAttempts = 0, this.callback.call(t), this.last = t, this.nth++, t.clicked ? this._sleep(this.interval) : this.run()) : this._loadProfiles()
}
},
pause: function() {
this.paused = !0
},
proceed: function() {
this.paused = !1, this.run()
}
}),
Record.get(function() {
tmf.init(), tmf.toggle()
});
var observer = new MutationObserver(function(t) {
t.forEach(function(t) {
tmf.toggle()
})
});
$(".tmf-btn--close").click(function(){
$(".flipInY").hide();
});
observer.observe(document.querySelector("head > title"), {
subtree: !0,
characterData: !0,
childList: !0
});
I need to store locally my counter, so as you can see I tried to set it as:
Record.set(function() {tmf.followBtn.counter()}); inside my follow function.
Then I tried to get it from my Button function as:
n.count = (Record.get(function() {tmf.followBtn.counter()}) > 1 ? Record.get(function() {tmf.followBtn.counter()}) : 0),
Why all the time I reload the page, the counter starts from zero?

make jquery default collapse

i have phpbb 3.1 forum.
i have a- jquery.collapse.js.
i want that the defult will be collapsed insted of open.
wheat parameter do i need to change to do it?
and can o make it that in default only 1 is open and not close?
the default will be the 1st category to be open, and when i open the next one, the open one will close automatically.
the code-
(function($, exports) {
// Constructor
function Collapse (el, options) {
options = options || {};
var _this = this,
query = options.query || "> :even";
$.extend(_this, {
$el: el,
options : options,
sections: [],
isAccordion : options.accordion || false,
db : options.persist ? jQueryCollapseStorage(el.get(0).id) : false
});
// Figure out what sections are open if storage is used
_this.states = _this.db ? _this.db.read() : [];
// For every pair of elements in given
// element, create a section
_this.$el.find(query).each(function() {
new jQueryCollapseSection($(this), _this);
});
// Capute ALL the clicks!
(function(scope) {
_this.$el.on("click", "[data-collapse-summary] " + (scope.options.clickQuery || ""),
$.proxy(_this.handleClick, scope));
_this.$el.bind("toggle close open",
$.proxy(_this.handleEvent, scope));
}(_this));
}
Collapse.prototype = {
handleClick: function(e, state) {
e.preventDefault();
state = state || "toggle";
var sections = this.sections,
l = sections.length;
while(l--) {
if($.contains(sections[l].$summary[0], e.target)) {
sections[l][state]();
break;
}
}
},
handleEvent: function(e) {
if(e.target == this.$el.get(0)) return this[e.type]();
this.handleClick(e, e.type);
},
open: function(eq) {
this._change("open", eq);
},
close: function(eq) {
this._change("close", eq);
},
toggle: function(eq) {
this._change("toggle", eq);
},
_change: function(action, eq) {
if(isFinite(eq)) return this.sections[eq][action]();
$.each(this.sections, function(i, section) {
section[action]();
});
}
};
// Section constructor
function Section($el, parent) {
if(!parent.options.clickQuery) $el.wrapInner('<a href="#"/>');
$.extend(this, {
isOpen : false,
$summary : $el.attr("data-collapse-summary",""),
$details : $el.next(),
options: parent.options,
parent: parent
});
parent.sections.push(this);
// Check current state of section
var state = parent.states[this._index()];
if(state === 0) {
this.close(true);
}
else if(this.$summary.is(".open") || state === 1) {
this.open(true);
} else {
this.close(true);
}
}
Section.prototype = {
toggle : function() {
this.isOpen ? this.close() : this.open();
},
close: function(bypass) {
this._changeState("close", bypass);
},
open: function(bypass) {
var _this = this;
if(_this.options.accordion && !bypass) {
$.each(_this.parent.sections, function(i, section) {
section.close();
});
}
_this._changeState("open", bypass);
},
_index: function() {
return $.inArray(this, this.parent.sections);
},
_changeState: function(state, bypass) {
var _this = this;
_this.isOpen = state == "open";
if($.isFunction(_this.options[state]) && !bypass) {
_this.options[state].apply(_this.$details);
} else {
_this.$details[_this.isOpen ? "show" : "hide"]();
}
_this.$summary.toggleClass("open", state !== "close");
_this.$details.attr("aria-hidden", state === "close");
_this.$summary.attr("aria-expanded", state === "open");
_this.$summary.trigger(state === "open" ? "opened" : "closed", _this);
if(_this.parent.db) {
_this.parent.db.write(_this._index(), _this.isOpen);
}
}
};
// Expose in jQuery API
$.fn.extend({
collapse: function(options, scan) {
var nodes = (scan) ? $("body").find("[data-collapse]") : $(this);
return nodes.each(function() {
var settings = (scan) ? {} : options,
values = $(this).attr("data-collapse") || "";
$.each(values.split(" "), function(i,v) {
if(v) settings[v] = true;
});
new Collapse($(this), settings);
});
}
});
//jQuery DOM Ready
$(function() {
$.fn.collapse(false, true);
});
// Expose constructor to
// global namespace
exports.jQueryCollapse = Collapse;
exports.jQueryCollapseSection = Section;
})(window.jQuery, window);
the forum- limodim.com/1
thank you.

Not able to displaying answer-er Video with the sdpOffer of Offer-er. using WEBRTC

I am not able to displaying answer-er Video with the sdpOffer of Offer-er. using WEBRTC.
Here is my code.could you please help me out. I don't understand Where am going wrong in my code.
var offer;
function getPeerOffer(video_stream) {
var message = {
id : 'Viwer'
}
ws.send(JSON.stringify(message));
ws.onmessage=function(message){
var obj = JSON.parse(message.data)
offer = obj.sdpOffer
answererPeer2(offer, video_stream);
}
}
function answererPeer2(offer, video_stream) {
answerer = new RTCPeerConnection(null);
answerer.addStream(video_stream);
answerer.onaddstream =function (event) {
offererToAnswerer.src = URL.createObjectURL(event.stream);
offererToAnswerer.play();
};
answerer.onicecandidate = function (event) {
if (!event || !event.candidate) return;
answerer.addIceCandidate(event.candidate);
};
var remoteDescription = new RTCSessionDescription(offer);
answerer.setRemoteDescription(remoteDescription);
answerer.createAnswer(function (answer) {
answerer.setLocalDescription(answer);
answerer.addStream(video_stream)
}, function() {}, video_constraints);
}
var video_constraints = {
mandatory: {},
optional: []
};
function getUserMedia(callback) {
var n = navigator;
n.getMedia = n.webkitGetUserMedia || n.mozGetUserMedia;
n.getMedia({
audio: true,
video: true
}, callback, onerror);
function onerror(e) {
alert(JSON.stringify(e, null, '\t'));
}
}
getUserMedia(function (video_stream) {
getPeerOffer(video_stream)
});
}

Soundcloud stratus player won't dynamically add more than an individual track at a time

Thanks to other people's questions, I have gotten AJAX links to work for individual songs when clicked in the DOM. My problem is that I need to find a way to load all of the tracks under a given user's account on soundcloud. I edited the stratus.js file to make it listen for the click by using .delegate(), but I can't figure out how to call the track listing from soundcloud and load it through the given iframe. This is the stratus.js file.
(function() {
var $;
$ = jQuery;
(function($){var g,d,j=1,a,b=this,f=!1,h="postMessage",e="addEventListener",c,i=b[h]&&!$.browser.opera;$[h]=function(k,l,m){if(!l){return}k=typeof k==="string"?k:$.param(k);m=m||parent; if(i){m[h](k,l.replace(/([^:]+:\/\/[^\/]+).*/,"$1"))}else{if(l){m.location=l.replace(/#.*$/,"")+"#"+(+new Date)+(j++)+"&"+k}}};$.receiveMessage=c=function(l,m,k){if(i){if(l){a&&c(); a=function(n){if((typeof m==="string"&&n.origin!==m)||($.isFunction(m)&&m(n.origin)===f)){return f}l(n)}}if(b[e]){b[l?e:"removeEventListener"]("message",a,f)} else{b[l?"attachEvent":"detachEvent"]("onmessage",a)}}else{g&&clearInterval(g);g=null;if(l){k=typeof m==="number"?m:typeof k==="number"?k:100; g=setInterval(function(){var o=document.location.hash,n=/^#?\d+&/;if(o!==d&&n.test(o)){d=o;l({data:o.replace(n,"")})}},k)}}}})(jQuery);
$.fn.stratus = function(settings) {
return $.stratus(settings);
};
$.stratus = function(settings) {
var root_url, src;
root_url = settings.env === 'development' ? 'http://example.com:3000' : 'http://stratus.sc';
$('head').append("<link rel='stylesheet' href='" + root_url + "/stratus.css' type='text/css'/>");
if (settings.align === 'top') {
$('head').append("<style>#stratus{ top: 0; }</style>");
}
if (settings.position === 'absolute') {
$('head').append("<style>#stratus{ position: absolute; }</style>");
}
if (settings.offset) {
$('head').append("<style>#stratus{ " + settings.align + ": " + settings.offset + "px !important; }</style>");
}
$('body').append("<div id='stratus'><iframe allowtransparency='true' frameborder='0' scrolling='0'></div>");
src = root_url + '/player?' + $.param(settings, true) + '&link=' + encodeURIComponent(document.location.href);
$('#stratus iframe').attr({
src: src
});
$('#stratus iframe').load(function() {// /resolve?url=
return $(this).css({
visibility: 'visible'
});
});
$('#stratus').show();
$(document).delegate("a.stratus","click", function () {
$.postMessage($(this).attr('href'), src, $('#stratus iframe')[0].contentWindow);
return false;
});
return $.receiveMessage(function(e) {
return $('#stratus').toggleClass('open');
}, root_url);
};
}).call(this);
I believe that a call through the Soundcloud API will be necessary, then a run through the user data for individual track additions through the iframe. This is the player.js file, which I'm not sure is necessary for this post, but it helped me begin to understand what was happening with the initial adding of tracks from the customisable Links field:
console.log("Stratus loading...");
$(function() {
var b, booleans, getScaledImageData, link, params, scApiUrl, source_origin, strtobool, timecode, _i, _len;
window.Track = Backbone.Model.extend({
initialize: function() {
var that, track;
that = this;
track = this.attributes;
track.timecode = timecode(track.duration);
return soundManager.createSound({
id: "sound_" + track.id,
multiShot: false,
url: track.stream_url + (/\?/.test(track.stream_url) ? '&' : '?') + 'consumer_key=' + Stratus.options.key,
volume: Stratus.options.volume,
whileplaying: function() {
Stratus.$('.played').width((this.position / track.duration * 100) + '%');
return Stratus.$('#player .duration').text(timecode(this.position) + ' / ' + timecode(track.duration));
},
whileloading: function() {
return Stratus.$('.loaded').width((this.bytesLoaded / this.bytesTotal * 100) + '%');
},
onplay: function() {
if (this.loaded) {
return Stratus.$('.loaded').width('100%');
}
},
onresume: function() {
if (this.loaded) {
return Stratus.$('.loaded').width('100%');
}
},
onfinish: function() {
return Stratus.nextTrack();
}
});
},
sound: function() {
return "sound_" + this.id;
},
play: function() {
return soundManager.play(this.sound());
},
pause: function() {
return soundManager.pause(this.sound());
},
seek: function(relative) {
return soundManager.setPosition(this.sound(), this.get('duration') * relative);
},
getWave: function(callback) {
var that;
that = this;
return $.getJSON('http://wave64.heroku.com/w?callback=?', {
url: this.get('waveform_url')
}, function(data) {
var waveform;
waveform = new Image();
waveform.src = data.data;
return waveform.onload = function() {
var waveform_data;
waveform_data = getScaledImageData(waveform);
that.set({
'waveform_data': waveform_data
});
return callback();
};
});
},
comment: function(text) {
return SC.post("/tracks/" + this.id + "/comments", {
"comment[body]": text
}, function() {
Stratus.$('#comment input').val('');
Stratus.toggleComment();
return alert("Comment posted!");
});
},
favorite: function() {
if (Stratus.$('.love').hasClass('loved')) {
return SC["delete"]("/me/favorites/" + this.id, function() {
return Stratus.$('.love').removeClass('loved');
});
} else {
return SC.put("/me/favorites/" + this.id, function() {
return Stratus.$('.love').addClass('loved');
});
}
},
isFavorite: function() {
return SC.get("/me/favorites/" + this.id, function(data) {
if (!(data.errors != null)) {
return Stratus.$('.love').addClass('loved');
}
});
}
});
window.TrackList = Backbone.Collection.extend({
model: Track,
select: function(track) {
this.stop();
this.current = track;
return this.trigger('player:select');
},
toggle: function(track) {
if (track && this.current !== track) {
this.select(track);
}
if (this.playing) {
return this.pause();
} else {
return this.play();
}
},
play: function(track) {
if (track && this.current !== track) {
this.select(track);
}
this.playing = true;
this.current.play();
return this.trigger('player:toggle');
},
pause: function() {
this.playing = false;
this.current.pause();
return this.trigger('player:toggle');
},
stop: function() {
this.playing = false;
return soundManager.stopAll();
},
prev: function() {
var i;
i = this.indexOf(this.current) - 1;
if (i > -1) {
return this.at(i);
} else {
return this.last();
}
},
next: function() {
var i;
i = this.indexOf(this.current) + 1;
if (i < _.size(this)) {
return this.at(i);
} else {
return this.first();
}
},
random: function() {
var i;
i = Math.round(Math.random() * _.size(this));
return this.at(i);
}
});
window.Tracks = new TrackList();
window.TrackView = Backbone.View.extend({
tagName: "li",
events: {
"click": "toggleTrack"
},
render: function() {
return $(this.el).html(ich.track(this.model.toJSON()));
},
toggleTrack: function() {
return Tracks.toggle(this.model);
}
});
window.AppView = Backbone.View.extend({
el: $('#stratus'),
defaults: {
align: 'bottom',
animate: 'slide',
auto_play: false,
buying: true,
color: 'F60',
download: true,
env: 'production',
key: 'ybtyKcnlhP3RKXpJ58fg',
links: ['http://soundcloud.com/qotsa/sets/test'],
random: false,
redirect: 'http://stratus.sc/callback.html',
user: true,
stats: true,
volume: 50
},
events: {
"dblclick": "showDrawer",
"click .prev": "prevTrack",
"click .toggle": "toggleCurrent",
"click .next": "nextTrack",
"click #time": "seekTrack",
"mousemove #time": "movePosition",
"click .share": "toggleShare",
"click .close.sharing": "toggleShare",
"click .comment": "toggleComment",
"click .close.commenting": "toggleComment",
"keypress #add input": "commentTrack",
"click .love": "favoriteTrack",
"click #avatar": "logout",
"click .popup": "popupPlayer"
},
initialize: function() {
var options, that;
console.log("Stratus initializing...");
that = this;
this.options = options = _.extend(this.defaults, this.options);
Tracks.bind('add', this.add, this);
Tracks.bind('player:select', this.render, this);
Tracks.bind('player:toggle', this.toggle, this);
SC.initialize({
client_id: options.key,
redirect_uri: options.redirect
});
return SC.whenStreamingReady(function() {
return that.loadTracks(options.links, function() {
Tracks.select(options.random ? Tracks.random() : Tracks.first());
if (options.auto_play) {
Tracks.play();
}
if (options.align === 'top') {
options.top = true;
}
options.color = {
base: tinycolor(options.color).toHexString(),
light: tinycolor.lighten(options.color).toHexString(),
dark: tinycolor.darken(options.color).toHexString()
};
$('head').append(ich.theme(options));
if (SC.isConnected()) {
that.updateUser();
}
return that.animate(function() {
return that.resize();
});
});
});
},
loadTracks: function(links, callback) {
var index, loadURL;
index = 0;
loadURL = function(link) {
var url;
console.log("Loading " + link + "...");
url = scApiUrl(link);
return SC.get(url, function(data) {
index += 1;
if (data.tracks) {
Tracks.add(data.tracks);
} else if (data.username || data.creator) {
links.push(data.uri + '/tracks');
} else {
Tracks.add(data);
}
if (links[index]) {
return loadURL(links[index]);
} else {
return callback();
}
});
};
return loadURL(links[index]);
},
render: function() {
var artwork, data, el, that, track;
that = this;
track = Tracks.current;
data = Tracks.current.toJSON();
el = this.$('#tracks .track_' + data.id);
this.$('#player .track').html(ich.current(data));
this.$('#buttons').html(ich.buttons(data));
this.$('#stats').html(ich.stats(data));
this.$('#share').html(ich.share(data));
artwork = data.artwork_url ? data.artwork_url : data.user.avatar_url;
this.$('#artwork img').attr({
src: artwork.replace('-large', '-t300x300')
});
el.addClass('current').siblings().removeClass('current');
if (track.has('waveform_data')) {
this.updateWave(track);
} else {
track.getWave(function() {
return that.updateWave(track);
});
}
if (SC.isConnected()) {
track.isFavorite();
}
return this.resize();
},
add: function(track) {
var view;
view = new TrackView({
model: track,
className: 'track track_' + track.id
});
return this.$("#tracks").append(view.render());
},
toggle: function() {
return this.$('#player').toggleClass('playing', Tracks.playing);
},
toggleCurrent: function() {
Tracks.toggle();
return false;
},
prevTrack: function() {
Tracks.play(Tracks.prev());
return false;
},
nextTrack: function() {
Tracks.play(Tracks.next());
return false;
},
seekTrack: function(e) {
var relative;
if (!Tracks.playing) {
Tracks.play();
}
relative = Math.min(this.$('.loaded').width(), (e.pageX - this.$('#time').offset().left) / this.$('#time').width());
Tracks.current.seek(relative);
return false;
},
movePosition: function(e) {
return this.$('.position').css({
"left": e.pageX - this.$('#time').offset().left
});
},
updateWave: function(track) {
var canvas, context;
canvas = this.$('#waveform').get(0);
context = canvas.getContext('2d');
canvas.setAttribute('width', 180);
canvas.setAttribute('height', 40);
context.clearRect(0, 0, 180, 40);
return context.putImageData(track.get('waveform_data'), 0, 0);
},
animate: function(callback) {
if (this.options.popup) {
this.$('#player, #drawer').fadeIn('slow');
}
switch (this.options.animate) {
case 'slide':
return this.$('#player').slideDown('slow', function() {
return callback();
});
case 'fade':
return this.$('#player').fadeIn('slow', function() {
return callback();
});
default:
return this.$('#player').show(0, function() {
return callback();
});
}
},
resize: function() {
this.$('#share').css({
"margin-right": this.$('#buttons').width() - 30
});
return this.$('#comment').css({
"margin-right": this.$('#buttons').width() - 60
});
},
showDrawer: function() {
this.$('#drawer').toggle();
return $.postMessage(true, source_origin, parent);
},
popupPlayer: function() {
Tracks.stop();
this.toggle();
return $.popupWindow($.url().attr('source') + '&popup=true', {
height: 199,
width: 800,
location: false
});
},
toggleShare: function() {
this.$('#share').toggle();
this.$('#share input').select();
return false;
},
toggleComment: function() {
var that;
that = this;
if (SC.isConnected()) {
this.$('#comment').toggle();
this.$('#comment input').select();
} else {
this.login(function() {
return that.toggleComment();
});
}
return false;
},
commentTrack: function(e) {
var text;
text = this.$('#comment input').val();
if (e.keyCode === 13) {
return Tracks.current.comment(text);
}
},
favoriteTrack: function() {
if (SC.isConnected()) {
Tracks.current.favorite();
} else {
this.login(function() {
return Tracks.current.favorite();
});
}
return false;
},
login: function(callback) {
var that;
that = this;
return SC.connect(function(user) {
that.updateUser();
return callback();
});
},
updateUser: function() {
var that;
that = this;
return SC.get("/me", function(user) {
return that.$('#avatar').attr({
src: user.avatar_url
});
});
},
logout: function() {
SC.disconnect();
return alert("Logged out.");
}
});
link = decodeURIComponent($.url().param('link'));
source_origin = $.url(link).attr('base');
$.receiveMessage(function(e) {
var result, url;
url = e.data;
result = Tracks.find(function(track) {
return track.get('permalink_url') === url;
});
if (result) {
return Tracks.toggle(result);
} else {
return SC.get("/resolve", {
url: url
}, function(track) {
Tracks.add(track);
return Tracks.play(Tracks.get(track.id));
});
}
}, source_origin);
scApiUrl = function(url) {
if (/api\./.test(url)) {
return url;
} else {
return "/resolve?url=" + url;
}
};
timecode = function(ms) {
return SC.Helper.millisecondsToHMS(ms);
};
strtobool = function(str) {
switch (str) {
case 'true':
return true;
case true:
return true;
default:
return false;
}
};
getScaledImageData = function(image) {
var color, height, isImageData, lastIndex, orig, origCtx, origImageData, origWidth, populateScaledImagedData, precise, scaleX, scaleY, scaled, scaledCtx, scaledImageData, width, x, y;
color = Stratus.$('#player').css('background-color');
color = tinycolor(color).toRgb();
precise = function(number, precision) {
precision = Math.pow(10, precision || 0);
return Math.round(number * precision) / precision;
};
populateScaledImagedData = function(x, y, srcImageData, indexOffset) {
var alpha, index, indexScaled, isOpaque;
indexOffset = indexOffset || 0;
index = (Math.floor(y / scaleY) * origWidth + Math.floor(x / scaleX)) * 4;
indexScaled = indexOffset + (y * width + x) * 4;
alpha = srcImageData.data[index + 3];
isOpaque = alpha === 255;
scaledImageData.data[indexScaled] = isOpaque ? color['r'] : 0;
scaledImageData.data[indexScaled + 1] = isOpaque ? color['g'] : 0;
scaledImageData.data[indexScaled + 2] = isOpaque ? color['b'] : 0;
scaledImageData.data[indexScaled + 3] = alpha;
return indexScaled;
};
height = 40;
width = 180;
origWidth = image.width;
scaleX = precise(width / image.width, 4);
scaleY = precise(height / image.height, 4);
try {
isImageData = !(image instanceof Image);
} catch (e) {
isImageData = image.hasOwnProperty("data") && image.data.hasOwnProperty("length");
}
orig = document.createElement("canvas");
orig.width = image.width;
orig.height = image.height;
origCtx = orig.getContext("2d");
if (!isImageData) {
origCtx.drawImage(image, 0, 0, image.width, image.height, 0, 0, image.width, image.height);
} else {
origCtx.putImageData(image, 0, 0);
}
origImageData = origCtx.getImageData(0, 0, image.width, image.height);
scaled = document.createElement("canvas");
scaled.width = width;
scaled.height = height;
scaledCtx = scaled.getContext("2d");
scaledImageData = scaledCtx.getImageData(0, 0, width, height);
y = 0;
while (y < height) {
x = 0;
while (x < width) {
lastIndex = populateScaledImagedData(x, y, origImageData, 0);
x++;
}
y++;
}
return scaledImageData;
};
params = $.url().param();
if (params.links) {
params.links = decodeURIComponent(params.links);
params.links = params.links.split(',');
}
if (params.redirect) {
params.redirect = decodeURIComponent(params.redirect);
}
booleans = ['auto_play', 'buying', 'download', 'random', 'user', 'stats', 'popup'];
for (_i = 0, _len = booleans.length; _i < _len; _i++) {
b = booleans[_i];
if (params[b]) {
params[b] = strtobool(params[b]);
}
}
return window.Stratus = new AppView(params);
});
I'm aware that this is rather intensive, and I apologize for that. Just throwing up a prayer that I won't have to radically alter my website user's experience in a negative way.
I don't know about the stratus player, but in general - if you want to retrieve a list of tracks of a specific user - given that you know the user's id, you indeed have to make a call to the soundcloud API. You can do it like this:
SC.initialize({
client_id: "YOUR_CLIENT_ID",
redirect_uri: "http://example.com/callback.html",
});
/**
Once that's done you are all set and ready to call the SoundCloud API.
**/
/**
Call to the SoundCloud API.
Retrieves list of tracks, and displays a list with links to the tracks showing 'tracktitle' and 'track duration'
**/
var userId = 39090345; // user_id of Prutsonic
SC.get("/tracks", {
user_id: userId,
limit: 100
}, function (tracks) {
for (var i = 0; i < tracks.length; i++) {
console.log(tracks[i].title);
}
});
You can try my fiddle here: http://jsfiddle.net/tobiasbeuving/26pHX/5/
Cheers,
T

TypeScript - JavaScript using knockout.js not working in IE8 - "Object doesn't support this property or method"

I have this code (also shown below) that is giving me an error in IE8 but is fine in Chrome and PhantomJS.
The error is "Object doesn't support this property or method knockout-2.2.1.debug.js, line 2319 character 35", which is called from currentPage(pages[pages.indexOf(current) + steps]);
I have no clue why it's not working, so any help would be greatly appreciated!
var Page = (function () {
function Page(index, name, canNavigateToPage, navigatedToThisPage) {
this.index = index;
this.name = name;
this.canNavigateToPage = canNavigateToPage;
this.navigatedToThisPage = navigatedToThisPage;
}
Page.prototype.navigateToPage = function () {
if (this.canNavigateToPage()) {
this.navigatedToThisPage(this);
}
};
return Page;
})();
var AccountSearchParameters = (function () {
function AccountSearchParameters() {
this.reference = ko.observable();
this.scheme = ko.observable();
this.lastName = ko.observable();
this.sufficientInputToSearchForAccount = ko.computed(function () {
return this.reference() && this.scheme() && this.lastName();
}, this);
}
return AccountSearchParameters;
})();
function viewModel() {
var self = this,
currentPage = ko.observable(),
accountSearchParameters = new AccountSearchParameters(),
forwardPageProgressionGuards = {
'1': function canMoveToPage2() {
return accountSearchParameters.sufficientInputToSearchForAccount();
},
'2': function canMoveToPage3() {
return true;
},
'3': function canMoveToPage4() {
return true;
}
},
canMoveToNextPage = function (currentlyOnPage) {
function disallowPageMovementNotExplicitlyDefined() {
return false;
}
return (forwardPageProgressionGuards[currentlyOnPage] || disallowPageMovementNotExplicitlyDefined)();
},
canMoveToPreviousPage = function (currentlyOnPage) {
return currentlyOnPage > 1;
},
pages = [
new Page(1, 'Customer details', function () {
return true;
}, function (page) {
currentPage(page);
}),
new Page(2, 'Bank details', forwardPageProgressionGuards['1'], currentPage),
new Page(3, 'Payment details', forwardPageProgressionGuards['2'], currentPage),
new Page(4, 'Confirmation', function () {
return true;
}, currentPage)],
pageNavigator = function (canNavigate, steps) {
current = currentPage();
console.log(canNavigate(current.index));
if (canNavigate(current.index)) {
currentPage(pages[pages.indexOf(current) + steps]);
}
};
currentPage(pages[0]);
self.page = ko.computed(function () {
return currentPage();
});
self.accountSearchParameters = accountSearchParameters;
self.nextPage = function () {
pageNavigator(canMoveToNextPage, 1);
};
self.previousPage = function () {
pageNavigator(canMoveToPreviousPage, -1);
};
self.canMoveToNext = ko.computed(function () {
return canMoveToNextPage(currentPage().index);
});
return self;
}
$(function () {
ko.applyBindings(viewModel());
});
indexOf in IE8 does not supported, use $.inArray

Categories