4

I am attempting to implement <audio> into a THREE.js scene and I am having some trouble with the audio playback. In my array that I am logging out to the console, All of the output is "0". I have accounted for the CORS issue by placing this: audio.crossOrigin = "anonymous" after the audio.play(); initialization, but to no avail has this helped. Any ideas would be appreciated.

Oh, I have also tested this with a server, so that's not the issue.

Here is a link to the repo.

Here is the jsbin.

Below is the code.

HTML

<html lang="en">

<head>
  <meta charset="UTF-8" />
  <title>Threejs Experiment</title>
  <link rel="stylesheet" href="style.css" media="screen" title="no title" charset="utf-8">
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r73/three.min.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
  <script src="https://dl.dropboxusercontent.com/u/3587259/Code/Threejs/OrbitControls.js"></script>
</head>

<body>
<audio id="song" src="https://soundcloud.com/madeon/madeon-cut-the-kid"></audio>
<script type="text/javascript" src="main.js"></script>
<script type="text/javascript" src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>

</body>

</html>

JavaScript

$(function(){

// Add Audio context and Audio
var ctx = new AudioContext();
var audio = document.getElementById('song');
var audioSrc = ctx.createMediaElementSource(audio);
var analyser = ctx.createAnalyser();

audioSrc.connect(analyser);
audioSrc.connect(ctx.destination);
var frequencyData = new Uint8Array(analyser.frequencyBinCount);

// Set scene and camera
var scene = new THREE.Scene();
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 100);
camera.target = new THREE.Vector3( 10, 10, 10 );

// Set the DOM
var renderer = new THREE.WebGLRenderer({ antialias:true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor("#000000");
document.body.appendChild(renderer.domElement);

// Add controls
// controls = new THREE.OrbitControls(camera, renderer );
// controls.addEventListener( 'change', render );

// Move the camera
camera.position.z = 10;
camera.position.y = 0;

// Point Lights

var pointLightBlue = new THREE.PointLight( "#00ccff", 5, 100, 2 );
pointLightBlue.position.set( -10, -40, -10 );
scene.add(pointLightBlue);

// var pointLightBlue = new THREE.PointLight( "#ffffff", 1, 0, 1 );
// pointLightBlue.position.set( -10, 20, -10 );
// scene.add(pointLightBlue);

// var pointLightPink = new THREE.PointLight( "#EE567C", 5, 100, 10 );
// pointLightPink.position.set( 1, 0, -5 );
// scene.add(pointLightPink);

var pointLight = new THREE.PointLight( "#A805FA", 100, 1000, 40 );
pointLight.position.set( 40, 0, 40 );
scene.add(pointLight);

var sphereSize = 5;
var pointLightHelper = new THREE.PointLightHelper( pointLight, sphereSize );
scene.add( pointLightHelper );

var pointLight2 = new THREE.PointLight( "#07FAA0", 100, 1000, 30 );
pointLight2.position.set( -40, 0, -40 );
scene.add(pointLight2);

var sphereSize = 5;
var pointLightHelper = new THREE.PointLightHelper( pointLight2, sphereSize );
scene.add( pointLightHelper );

var quantity = 50;
var shapes = [];


for (var i = 0; i < quantity; i++) {

    if(Math.random() < 0.5){
      var geometry = new THREE.RingGeometry( 50, 50, 18);

      // var geometries = [
            //      new THREE.IcosahedronGeometry( 20, 0 ),
            //      new THREE.OctahedronGeometry( 20, 0 ),
            //      new THREE.TetrahedronGeometry( 20, 0 ),
            //  ];
      //
            // var geometry = geometries[ Math.floor( Math.random() * geometries.length ) ];
            // var material = new THREE.MeshLambertMaterial( {
            //  color: new THREE.Color( Math.random(), Math.random() * 0.5, Math.random() ),
            //  blending: THREE.AdditiveBlending,
            //  depthTest: false,
            //  shading: THREE.FlatShading,
            //  transparent: true
            // } );
            // var mesh = new THREE.Mesh( geometry, material );
            // var wireframe = mesh.clone();
            // wireframe.material = wireframe.material.clone();
            // wireframe.material.wireframe = true;
            // mesh.add( wireframe );
            // scene.add(mesh);

      // var geometry = new THREE.RingGeometry( 20, 150, 18);

        //var geometry = new THREE.TorusKnotGeometry( 10, 3, 100, 16 );
    }
    else {
      // var geometry = new THREE.RingGeometry( 4, 40, 3);


      // var geometry = new THREE.RingGeometry( 1, 5, 6 );
      // var material = new THREE.MeshBasicMaterial( { color: 0xffff00,
      //   side: THREE.DoubleSide } );
      // var mesh = new THREE.Mesh( geometry, material );
      // scene.add( mesh );

      // var points = [];
      // for ( var j = 0; j < 10; j++ ) {
      //    points.push( new THREE.Vector3( Math.sin( j * 0.2 ) * 15 + 50, 0, ( j - 5 ) * 2 ) );
      //
      // }
      // var geometry = new THREE.LatheGeometry( points );
      // var material = new THREE.MeshBasicMaterial( { color: 0xffff00 } );
      // var lathe = new THREE.Mesh( geometry, material );
      // scene.add( lathe );
    }

    if(i % 7 === 0) {
        var material = new THREE.MeshPhongMaterial( { color: "#ffffff"} );
    }
    else if(i % 2 === 0){
        var material = new THREE.MeshPhongMaterial( { color: "#666666"} );
    }
    else {
        var material = new THREE.MeshPhongMaterial( { color: "#333333"} );
    }

    var mesh = new THREE.Mesh(geometry, material);
    mesh.position.z = -i * 2;
    mesh.rotation.z = i;
    shapes.push(mesh);
    scene.add(mesh);
}

// Variables
var u_time = 0;

// Render function
var render = function() {
    requestAnimationFrame(render);
    u_time++;

    for (var i = 0; i < quantity; i++) {

        // Set rotation change of shapes
        shapes[i].position.z += 0.2;
        shapes[i].rotation.z += 0;
        shapes[i].scale.x = 1 + Math.sin(i + u_time * 0.1) * 0.05;
        shapes[i].scale.y = 1 + Math.sin(i + u_time * 0.1) * 0.05;

        var change = 1.5 + Math.sin(u_time * 0.5) * 0.5;

        // Set wireframe & width
        if(Math.random() < change){
            shapes[i].material.wireframe = true;
            shapes[i].material.wireframeLinewidth = Math.random() * 2;
        }
        else {
            shapes[i].material.wireframe = false;
        }

        if(shapes[i].position.z > 10){
            shapes[i].position.z = -70;
            shapes[i].rotation.z = i;
        }
    }

    // Set Point light Intensity & Position
    pointLight.intensity = Math.abs(Math.sin(u_time * 0.2) * 2);
    pointLight2.intensity = Math.abs(Math.cos(u_time * 0.2) * 2);
    pointLight.position.z = Math.abs(Math.sin(u_time * 0.02) * 30);
    pointLight2.position.z = Math.abs(Math.cos(u_time * 0.02) * 30);

    // camera.rotation.y = 90 * Math.PI / 180;
    // camera.rotation.z = frequencyData[20] * Math.PI / 180;
    // camera.rotation.x = frequencyData[100] * Math.PI / 180;
    console.log(frequencyData);

    //composer.render();
    renderer.render(scene, camera);

}
audio.play();
// audio.crossOrigin = "anonymous";
render();
});
gman
  • 100,619
  • 31
  • 269
  • 393
jshuadvd
  • 552
  • 5
  • 16
  • a) in 99% of cases, you can't fix CORS issues with client side code - CORS allows a server to grant cross origin access, a client can't force it. b) in your code you have NOT added `audio.crossOrigin = "anonymous";` - you've added a comment with the content `audio.crossOrigin = "anonymous";` - so, have done nothing – Jaromanda X Jan 14 '16 at 05:41
  • I have a server to test if that was an issue. And I have tested with the 'audio.crossOrigin = " anonymous";' uncommented. Neither worked. – jshuadvd Jan 14 '16 at 06:00
  • So what you're saying is that adding audio. crossOrigin made no difference. Proving my point a) – Jaromanda X Jan 14 '16 at 06:04
  • It did not make any difference. Can you tell me what I need to do in order to make this work properly, because I already knew what you mentioned previously.? – jshuadvd Jan 14 '16 at 06:08
  • The server needs to allow cross origin requests – Jaromanda X Jan 14 '16 at 06:08
  • I see. Would I just add that line of code in the server then? according to this article: http://stackoverflow.com/questions/31308679/mediaelementaudiosource-outputs-zeros-due-to-cors-access-restrictions-local-mp3 , he says just to place it after the audio.play() is initialized. – jshuadvd Jan 14 '16 at 06:35
  • Have you read the comment `Only if the server has the correct Access-Control-Allow-Origin setting, which is really unlikely to happen if OP keeps trying to get files from his filesystem (not a server` – Jaromanda X Jan 14 '16 at 06:38
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/100632/discussion-between-jshuadvd-and-jaromanda-x). – jshuadvd Jan 14 '16 at 06:39

1 Answers1

11

What do you mean "CORS isn't an issue"?

  1. if the audio is coming from a different origin you need to set the crossOrigin property BEFORE setting the src property

    someAudioElement.crossOrigin = "anonymous";
    

    In other words either put the crossOrigin property in the tag itself

    <audio crossOrigin="anonymous" src="http://somedomain/someaudio">...
    

    Or in JavaScript

    someAudioElement.crossOrigin = "anonymous";
    someAudioElement.src = "http://somedomain/someaudio";
    

    The reason you have to do this before is because the moment you set the src property the browser will try to get the audio. If you haven't already set the crossOrigin property the browser won't know to ask for permission to use the audio.

    Note that all that does is ask the server for permission. The server itself still has to give permission to the browser to use the audio. Most servers do not give this permission. Soundcloud does but not for all songs.

  2. Safari on iOS and Chrome on Android don't support analysing streaming audio data.

    You can track the Chrome issue to fix this here. Star it if you want to track the progress. I don't know if there is a webkit bug to track the issue on iOS

  3. AFAIK you can't play soundcloud URLs directly

    You have code like this

    <audio id="song" src="https://soundcloud.com/madeon/madeon-cut-the-kid"></audio>
    

    That is not a valid URL for soundcloud audio. You have to pass that URL to the soundcloud API along with an API key and it will give you back a correct URL.

    Here's an SO question about it

    Other things to note:

    • The soundcloud SDK 3.0 SDK doesn't work on Safari for analysing audio. Use the soundcloud SDK 2.0 or better, use a direct XMLHttpRequest because the soundcloud SDK 2.0 initializes flash even if you don't use it.

    • Example using the soundcloud sdk 2.0

      in HTML

      <script src="https://connect.soundcloud.com/sdk-2.0.0.js"></script> 
      

      in Javascript

      // see https://auth0.com/docs/connections/social/soundcloud
      var yourSoundCloudClientId = "???????";
      SC.initialize({
        client_id: yoursCoundCloudClientId,
      });
      
      var soundCloudURL = "https://soundcloud.com/madeon/madeon-cut-the-kid";
      SC.get("/resolve", { url: soundCloudUrl }, function(result, err) {
        if (err) {
          console.error("bad url:", url, err);
          return;
        }
        if (result.streamable && result.stream_url) {
          var src = result.stream_url + '?client_id=' + yourSoundCloudClientId;
          someAudioElement.crossOrigin = "anonymous";
          someAudioElement.src = src;
      
          // other things you might be interested in because
          // if you use soundcloud music you're required to give
          // and display attribution and links
      
          console.log("link to music:", result.permalink_url);
          console.log("link to band:", result.user.permalink_url);
          console.log("name of song:", result.title);
          console.log("name to band:", result.user.username);
        } else {
           console.error("not streamable:", url);
        }
      });
      

      This example does it without the soundcloud SDK.

Community
  • 1
  • 1
gman
  • 100,619
  • 31
  • 269
  • 393
  • I meant CORS wasn't an issue because if I pulled up my `audio.crossOrigin` in the console, the property was set to `"anonymous"` . I see. Do you know of any other options to make this work? I have added a file locally, but audio still doesn't play for me. – jshuadvd Jan 15 '16 at 03:46
  • Here's 2 working programs, [one](http://twgljs.org/examples/dynamic-buffers.html) uses same origin [source](http://github.com/greggman/twgl.js). [two](http://greggman.github.io/vertexshaderart/src/index.html?settings=audio) uses cross origin. [source](http://github.com/greggman/vertexshaderart). Maybe start with one of those and see what you're doing differently. Also updated the answer. Make sure you're setting `crossOrigin` before requesting the audio. – gman Jan 15 '16 at 04:01
  • Also added #3 above. You can't use soundcloud URLs directly AFAIK – gman Jan 15 '16 at 04:12
  • 1
    Thank you for all of you help and information. I have almost working except for 2 things. The audio plays, but no sound is output. Also the `frequencyData` is still non existent. I am using a local audio file currently. The error in my console `MediaElementAudioSource outputs zeroes due to CORS access restrictions for ` but when I add back in the `crossOrigin = "anonymous"` as you mentioned nothing is playing and the error goes away. – jshuadvd Jan 15 '16 at 04:50
  • I also have `app.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); next(); }); ` in my express server to allow that to work. I am totally lost at this point. The github repo above reflects the changes I have made. I am only using Chrome on OSX – jshuadvd Jan 15 '16 at 04:51
  • CORS requires you respond to the `OPTIONS` http request. [Something like this](http://pastebin.com/HRT0pnDw). Though if your serving from the same server you don't need CORS. You can download that [first sample I linked to](http://github.com/greggman/twgl.js). It runs just fine with the standard node http-server. No code to write on the server. No CORS. `sudo npm install -g http-server`, `cd twgl.js`, `http-server`, then go to `http://localhost:8080/examples/dynamic-buffers.html` – gman Jan 15 '16 at 04:56
  • Ahh. Ok. I'll check that out. I also see that you just posted code on your answer. I'll look into this and get back to you ASAP. Just for clarification... I don't need what I added in the sever since everything is local ? – jshuadvd Jan 15 '16 at 05:00
  • Correct. If everything is coming from the same server then no CORS needed (for example everything coming from local). Note that includes the audio. If your audio URL is still pointing to soundcloud that's not local. – gman Jan 15 '16 at 05:01
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/100730/discussion-between-jshuadvd-and-gman). – jshuadvd Jan 15 '16 at 05:40
  • My advice is as I said before, start with a working program, once you know that works figure out what's different between yours and the working program. This program is pretty small http://twgljs.org/examples/dynamic-buffers.html The source is here http://github.com/greggman/twgl.js steps: `git clone https://github.com/greggman/twgl.js.git`, `cd twgl.js`, `sudo npm install -g http-server`, `http-server` then go to `http://localhost:8080/examples/dynamic-buffers.html`. That *should* work. It's tiny. Compare to yours. – gman Jan 15 '16 at 08:49
  • @gman im facing similar issues to this. can you check out some of my recent posts about it. id appreciate your help! – Juliette Aug 03 '21 at 00:29