Showing posts with label soundcloud. Show all posts
Showing posts with label soundcloud. Show all posts

Sunday, July 23, 2017

Soundcloud API returning incorrect values for Likes and Following

Leave a Comment

The Soundcloud API is returning incorrect counts for favourites (likes) and following.

Does anyone know how to fix or if it's just something on soundclouds side?

Example: User 29084746 is Jorja Smith, she has 29 likes. https://soundcloud.com/jorjasmith

https://api.soundcloud.com/users/29084746/favorites?client_id=XXX&page_size=200&linked_partitioning=1

returns 17 likes.

Edit: Just to clarify, this is happening with every user I test, not just this example.

2 Answers

Answers 1

Using this question (asked in 2016) as a reference, I found that:

There is a setting for Premium account that can prevent the track from showing as a like in third-party software.

So, some tracks are most likely hidden from third parties, and thus from the API.

this is happening with every user I test

Well, maybe now they are allowing every user to hide their posts from API access, as pointed out by sauntimo's answer.

Answers 2

I found an API key (took maybe 37 seconds on google...) and got the likes for that user from the API and stuck them in a table in this fiddle. I went down the list until I found one on the site that wasn't returned by the API. The first one was this track "Sharpness". Also, good heavens did I feel tragically uncool reading some of this music stuff I don't understand.

Anyway, I found the artist id for the missing "sharpness" track by doing a user search like this. Note that track_count in the returned object is 79. I then got the artists tracks like this (as per the API docs) and was intrigued to see that [ ] was returned. I did spot that this user was on a free plan according to the response, so it doesn't look like it's something to do with the artist having a premium account. Also whilst Jora has a premium account it seems unlikely that this would only hide certain likes.

I then repeated this process for another missing track, "U-GO-I-GO", found the artist here with track_count : 32 and then got their tracks which again came back as an empty array, [ ].

In conclusion it seems like there must be a setting for users to prevent API access to tracks which they upload. Then, when another user likes one of these users' tracks, these tracks are not returned by queries on the second users likes, hence the discrepancy in number of likes on the site vs returned by the API for your original user Jora Smith.

Further to this, in the help center there is an article on Disabling App Playback which states

If you would like to restrict playback of your track, you can choose to disable app playback through your track's Permissions tab. This means that your track will only be playable on SoundCloud and through SoundCloud embeds, instead of websites or apps that use our API.

Soundcloud Screenshot

I suspect that means that where the uploading user has disabled "app playback", these tracks are completely unavailable to the api. Note that it definitely seems to be the uploading user's permissions settings which are causing your discrepancy, not the user who's likes you're retrieving.

Read More

Monday, February 6, 2017

Is it possible to allow a SoundCloud track uploaded using the API to be embeddable by all?

Leave a Comment

I'm using the SoundCloud API to automatically upload audio tracks to SoundCloud.

Everything works OK, except the embeddable_by property value. Whatever it's set to, the track will always result in embeddable_by == "none".

Enabling the (undocumented) embeddable property results in embeddable_by == "me", but I need it to be "all" so the tracks can be embedded on external sites.

Editing the track permissions from SoundCloud and checking "Display embed code" effectively results in embeddable_by == "all".

Is there a way to accomplish this using only the API?

0 Answers

Read More

Sunday, October 2, 2016

Soundcloud embedded player on mobile

Leave a Comment

Here is how a SoundCloud embedded player on a HTML page looks like on mobile device:

enter image description here

It's rather annoying, because the user has to click "Listen in browser", and then, often, it doesn't start like it should, and so the user has to click "Pause" button and "Play" again.

How to have the normal look, even on mobile devices? :

enter image description here


Here is example of embedding code:

<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/271188615&amp;color=ff5500&amp;auto_play=false&amp;hide_related=false&amp;show_comments=true&amp;show_user=true&amp;show_reposts=false"></iframe> 

2 Answers

Answers 1

I am going to suggest not using an embedded iframe for the player and instead use SoundCloud's HTTP API

My answer does not focus on any methods to trick the embedded iframe code into not thinking it is mobile. Instead I am showing an alternative path to how to do your own native SoundCloud player.

Doing this guarantees:

  • You have full control over your UI
  • You have full control over playback

I've gone ahead and built a sample application in Android. Assuming you are looking for Android here because of the status bar in the posted question's image.

Also as requested there is a web project, that will work on mobile. The web project is using the SoundCloud api JavaScript wrapper. Oddly there appears to be a bug I am seeing on mobile. I tested on nexus 6P and iPhone 6. Their player they provide doesn't start playing on page load, appears to be some sort of buffering issue. I will update when I know more. The Web player is working but requires pressing play button twice similar to how you described the issue with the embedded player. I will look into the buffering issue when I have more time.

You can find my example project here:

Web Project: https://github.com/davethomas11/stackoverlow_Q_39625513/tree/master/WebPlayer hosted here -> https://www.daveanthonythomas.com/remote/so39625513/

Android: https://github.com/davethomas11/stackoverlow_Q_39625513/SoundCloudPlayer

Check it out, and ask me any questions regarding implementation if anything is not clear. That goes for anyone reading this answer.

The solution is done natively in Java. But it could also be done in HTML and Javascript if that is what you prefer, because we are using their HTTP Rest API the platform does not matter.

Going completely custom, this way gives us full control over the UI. My UI isn't the most beautiful, but it can be as ugly or as beautiful as you want with this level of control ;) ->

SoundCloudPlayer

I will break down the basic steps of using sound cloud's api to accomplish this.

Luckily for us playback is very straight forward. You can skip all of the authentication requirements. As any endpoints you will be using do not require authentication.

All you need is a client id to make your requests. I recommend registering an app with sound cloud, but you can use the embedded player's client id like I did.

Note: the embedded player uses the client id -> cUa40O3Jg3Emvp6Tv4U6ymYYO50NUGpJ

The basis of this implementation is the tracks endpoint: https://developers.soundcloud.com/docs/api/reference#tracks

This endpoint gives us almost everything we need:

  • streaming url
  • title, artist name
  • artwork

But there is one thing missing and that is the waveform data points to display SoundCloud's brand identifying wave form.

The basics of getting this data requires a little bit of hacking. But the data is there in a pure enough form to use.

If you inspect the response of a call to get the embedded player, you'll notice a resource being loaded in the source code by the name of waveform_url. This url returns a nice json document with all the wave point information: https://wis.sndcdn.com/sTEoteC5oW3r_m.json

I've adapted my solution to parse the wave form data from the embedded player, by retrieving it from that url.

You'll notice I've made a very crude version. With a little elbow grease this can be turned into something nice, and even unique. But the basics are there for acquiring it.

enter image description here

Another endpoint I have implemented in my solution is the comments endpoint: https://developers.soundcloud.com/docs/api/reference#comments

I have not yet added it to the UI. But the API code should shed some light onto it's use.

The Android project uses the following libraries:

And for those not familiar, since it is semi new: - Android DataBinding https://developer.android.com/topic/libraries/data-binding/index.html

Please feel free to use my solution as a base, as I've released it under the GNU license. That goes to anyone reading this.

I'd like to consider adding a similar iOS solution to the git-hub repository too as well.

Here is the web project as a snippet:

/*!   * jQuery UI Touch Punch 0.2.3   *   * Copyright 2011–2014, Dave Furfero   * Dual licensed under the MIT or GPL Version 2 licenses.   *   * Depends:   *  jquery.ui.widget.js   *  jquery.ui.mouse.js   */  !function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);    /**   * Created by dave on 16-09-26.   */  function WaveForm(waveformPngUrl) {        var self = this;        var heightRatio;      var waveformUrl = waveformPngUrl.replace(/png$/, "json");      waveformUrl = waveformUrl.replace(/:\/\/w1/,"://wis");        var canvas = $('#SoundCloudPlayer .track_waveform');      var heightPX = canvas.get(0).height;      var data;      var progress = 0;        this.load = function () {            $.get(waveformUrl, function (response) {                heightRatio = heightPX / response.height;              data = response;              canvas.css({ height: "" + heightPX + "px" });              self.draw();          });        };        this.draw = function () {            var ctx = canvas.get(0).getContext("2d");          ctx.clearRect(0, 0, canvas.get(0).width, canvas.get(0).height);            var x = 0;              var lineWidth = data.samples.length / canvas.width();          var progressPoint =  canvas.get(0).width - ((1 - progress) * canvas.get(0).width);            ctx.beginPath();          ctx.lineWidth = lineWidth / 2;          ctx.strokeStyle = "#ff8000";          var progressFound = false;            for (var i = 0; i < data.samples.length; i++) {                if (x > progressPoint && !progressFound) {                  ctx.stroke();                  ctx.closePath();                  ctx.beginPath();                  ctx.strokeStyle = "#000";                  progressFound = true;              }                var ratio = (data.samples[i] * heightRatio) / heightPX;              var drawTo = heightPX - ratio * heightPX;                ctx.moveTo(x, heightPX);              ctx.lineTo(x, drawTo);                x += lineWidth;          }            ctx.stroke();          ctx.closePath();        };        this.setProgress = function (newProgress) {          progress = newProgress;          self.draw();      }  }    var player, mTrack, media, seekBarInterval, waveForm;  var updatingSeekBar = false;    $(function () {        SC.initialize({          client_id: 'cUa40O3Jg3Emvp6Tv4U6ymYYO50NUGpJ'      });        player = document.getElementById("SoundCloudPlayer");        checkQueryURLForTrackId();      loadTrackEnteredInInput();        $("form button").button();  });    function loadTrackEnteredInInput() {        loadTrack(getTrackId());  }    function loadTrack(trackId) {        SC.get('/tracks/' + trackId).then(function (track) {            // Inspect for info on track you want:          console.log(track);          mTrack = track;            renderTrack(track);          streamTrack(track);            waveForm = new WaveForm(track.waveform_url);          waveForm.load();        }, function () {            alert("Sorry no track found for track id: "+ trackId)      });  }    function renderTrack(track) {        $(player).find(".track_artist").text(track.user.permalink);      $(player).find(".track_title").text(track.title);      $(player).find(".track_artwork").attr('src', track.artwork_url);      $(player).find(".track_seek_bar").slider(          {              orientation: "horizontal",              range: "min",              max: track.duration,              value: 0,              change: seek          });    }    function streamTrack(track) {        SC.stream('/tracks/' + track.id).then(function (mediaPlayer) {          media = mediaPlayer;            console.log(media);            play();      });  }    function play() {      if (!media) {          return;      }        $(player).find(".track_play").hide();      $(player).find(".track_pause").fadeIn();        media.play();        seekBarInterval = setInterval(updateSeekBar, 500);  }    function pause() {      if (!media) {          return;      }        $(player).find(".track_pause").hide();      $(player).find(".track_play").fadeIn();      media.pause();        clearInterval(seekBarInterval);  }    function seek() {      if (!media) {          return;      }        if (!updatingSeekBar) {          media.seek($(player).find(".track_seek_bar").slider("value"));      }  }    function updateSeekBar() {      if (!media) {          return;      }        waveForm.setProgress(media.currentTime() / mTrack.duration);        updatingSeekBar = true;      $(player).find(".track_seek_bar").slider("value", media.currentTime());      updatingSeekBar = false;  }    /**   * Loads a different track id based on   * url query   */  function checkQueryURLForTrackId() {      var query = getUrlVars();      if (query.trackId) {          $('[name=trackId]').val(query.trackId);      }  }    //http://stackoverflow.com/questions/4656843/jquery-get-querystring-from-url  // Read a page's GET URL variables and return them as an associative array.  function getUrlVars()  {      var vars = {}, hash;      var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');      for(var i = 0; i < hashes.length; i++)      {          hash = hashes[i].split('=');          vars[hash[0]] = hash[1];      }      return vars;  }    function getTrackId() {      return trackId = $('[name=trackId]').val();  }
body {      font-family: 'Raleway', sans-serif;  }    #SoundCloudPlayer .track_artwork {      float:left;      margin-right: 6px;  }    #SoundCloudPlayer .track_artist {      font-size: small;      margin-bottom: 4px;  }    #SoundCloudPlayer .track_title {      margin-top: 0px;      font-weight: bold;  }    #SoundCloudPlayer .track_control {      cursor: pointer;      display: none;  }    #SoundCloudPlayer .track_seek_bar .ui-slider-range { background: orange; }  #SoundCloudPlayer .track_seek_bar .ui-slider-handle { border-color: orange; }    #SoundCloudPlayer .track_waveform {      width: 100%;  }
<html>  <head>      <meta name="viewport" content="initial-scale=1, maximum-scale=1">      <title>SoundCloud API Web Player Demo</title>      <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>      <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.0/themes/smoothness/jquery-ui.css" />      <script src="jquery.ui.touch-punch.min.js"></script>      <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js"></script>      <script src="https://connect.soundcloud.com/sdk/sdk-3.1.2.js"></script>      <link href="https://fonts.googleapis.com/css?family=Raleway" rel="stylesheet" />      <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />  </head>  <body>    <form method="get">      <label for="trackId">Load Track:</label>      <input name="trackId" type="text" value="271188615" />      <button>GO</button>  </form>    <section id="SoundCloudPlayer">        <img class="track_artwork" />      <p class="track_artist"></p>      <p class="track_title"></p>      <i class="material-icons track_play track_control" onClick="play()">play_circle_filled</i>      <i class="material-icons track_pause track_control" onClick="pause()">pause_circle_filled</i>      <canvas class="track_waveform"></canvas>      <div class="track_seek_bar" ></div>  </section>  </body>  </html>

Answers 2

Mini Player (height=20) has similar look & feel for desktops and mobiles.

<iframe width="100%" height="20" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/271188615&color=ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false"></iframe> 
Read More

Saturday, August 20, 2016

Reliable way of getting a Soundcloud track playback count via the API?

Leave a Comment

I'm using the /tracks API endpoint to retrieve song stats like playback count and favorite count. I've noticed that the playback count in the API is not the same as the one on the website.

Here's an example Bug example

I found a workaround here http://stackoverflow.com/a/36985629/287491 but it seems that using it might be against the Soundcloud ToS although I didn't find the specific article in the ToS. On top of that I'd rather not use an undocumented API as it may change or be removed without notice.

What would be the proper way to retrieve the real playback count for a track?

1 Answers

Answers 1

As You wrote the workaround is valid but it surely violates the ToS of SoundCloud!

By the side of SoundCloud there was an attempt to clean up:

http://status.soundcloud.com/post/145855742725/stats-maintenance#note-container

Your is after their update (August 3rd, 2016 ), so it is a bug. My recommendation is to ask help on the forum, (stat special part), they seem to reply in this topic little to no time.

Read More

Wednesday, April 27, 2016

Parallax effect on each item in a recycler view?

Leave a Comment

I'm trying to play with Parallax and getting some weird bugs, wondering if anyone can add some input to it. The only app I've seen implement parallax effectively is soundcloud. It's quite subtle, but each item has an image background and it has he parallax effect as you scroll.

I've created a custom RecyclerView to handle this, here is what I have so far:

public class ParallaxScrollListener extends RecyclerView.OnScrollListener {  private float scrollSpeed = 0.5f;  @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) {     super.onScrolled(recyclerView, dx, dy);     LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();      int firstVisible = layoutManager.findFirstVisibleItemPosition();     int visibleCount = Math.abs(firstVisible - layoutManager.findLastVisibleItemPosition());      Matrix imageMatrix;     float tempSpeed = -100;      if (dy > 0) {         tempSpeed = scrollSpeed;     } else if (dy < 0) {         tempSpeed = -scrollSpeed;     }      for (int i = firstVisible; i < (firstVisible + visibleCount); i++) {         ImageView imageView = ((MyClass.MyAdapter.MyViewHolder) recyclerView.getLayoutManager().findViewByPosition(i).getTag()).image;         if (imageView != null) {             imageMatrix = imageView.getImageMatrix();             imageMatrix.postTranslate(0, tempSpeed);             imageView.setImageMatrix(imageMatrix);             imageView.invalidate();         }     } } 

In my RecyclerView Adapter's onBindView I have the following as well:

 Matrix matrix = viewHolder.image.getImageMatrix();  matrix.postTranslate(0, 0);  viewHolder.image.setImageMatrix(matrix);  viewHolder.itemView.setTag(viewHolder); 

Finally inside the onViewRecycled method I have the following:

@Override     public void onViewRecycled(MyViewHolder viewHolder) {         super.onViewRecycled(viewHolder);         if (viewHolder.image != null) {             viewHolder.image.setScaleType(ImageView.ScaleType.MATRIX);             Matrix matrix = viewHolder.image.getImageMatrix();             // this is set manually to show to the center             matrix.reset();             viewHolder.image.setImageMatrix(matrix);         } } 

I been working with this code on Github to get the idea

So the parallax works, but but views in my RecyclerView move as well. I have a CardView beneath the image and it moves, creating big gaps between each item. Scrolling is what causes this, the more the scroll up and down the bigger the gaps get, and the images get smaller as the parallax moves them out of their bounds.

I've tried messing with the numbers like scrollSpeed in the OnScrollListener but while it reduces the bug it also reduces the parallax.

Has anyone got any ideas on how I can achieve a bug free parallax effect on each item in my RecyclerView? I feel like I'm getting somewhere with this but it's still very buggy and I don't know what the next step is.

P.s I've tried looking at 3rd party libraries but they all seem to only use header parallax like the CoordinatorLayout, I haven't found any that do it just on each item in a list.

I'm hoping this question gets a good discussion going even if I don't solve my problem because Parallax seems to be underused in Android and there's very little around about it.

Thanks for you time, appreciate any help.

2 Answers

Answers 1

I managed to get Parallax working with this library: https://github.com/yayaa/ParallaxRecyclerView

For anyone doing this themselves, it's still a good thing to play and see how it works.

Similar concept to my code but it actually works! haha.

Answers 2

You are on the right track. You have to use a ScrollListener. Furtermore, you have to access RecyclerViews LayoutManager and iterate over all items that are visible and set translateY according to the amount of pixels scrolled.

The things get a little bit more complicated, because you can't use recyclerView.getChildAt(pos) because LayoutManager is responsible to layout elements and they might be in different order in the LayoutManager than getChildAt(pos).

So the algorithm basically should look like this (pseudo code, assuming LinearLayoutManager is used):

for (int i = layoutManager.findFirstVisibleItemPosition(); i <= layoutmanager.findLastVisibleItemPosition; i++){     // i is the adapter position     ViewHolder vh = recyclerView.findViewHolderForAdapterPosition(i);    vh.imageView.setTranslationY( computedParalaxOffset ); // assuming ViewHolder has a imageView field on which you want to apply the parallax effect } 
Read More