themirror
themirror

Reputation: 10287

Javascript: Airplay in the browser

Is there a way to send images, videos, and audio to an AirPlay server using JavaScript in the browser?

Upvotes: 5

Views: 4321

Answers (2)

amay0048
amay0048

Reputation: 1091

This Works for me

    var xhr = new XMLHttpRequest(),
        xhr_stop = new XMLHttpRequest(),
        hostname = "apple-tv.local",
        port =":7000",
        position = "0";


    xhr_stop.open("POST", "http://" + hostname + port + "/stop", true, "AirPlay", null);
    xhr_stop.send(null);

    xhr.open("POST", "http://" + hostname + port + "/play", true, "AirPlay", null);
    xhr.setRequestHeader("Content-Type", "text/parameters");
    xhr.send("Content-Location: " + url + "\nStart-Position: " + position + "\n");

    // set timer to prevent playback from aborting
    xhr.addEventListener("load", function() { 

        var timer = setInterval(function() {

            var xhr = new XMLHttpRequest(),
                // 0 something wrong; 2 ready to play; >2 playing
                playback_info_keys_count = 0,
                terminate_loop, playback_started;

            xhr.open("GET", "http://" + hostname + port + "/playback-info", true, "AirPlay", null);

            xhr.addEventListener("load", function() {

                playback_info_keys_count = xhr.responseXML.getElementsByTagName("key").length;
                console.log("playback: " + playback_started + "; keys: " + playback_info_keys_count)

                // if we're getting some actual playback info
                if (!playback_started && playback_info_keys_count > 2) {
                    playback_started = true;
                    console.log("setting playback_started = true")
                    terminate_loop = false;
                }

                // playback terminated 
                if (terminate_loop && playback_info_keys_count <= 2) {
                    console.log("stopping loop & setting playback_started = false")
                    clearInterval(timer);
                    var xhr_stop = new XMLHttpRequest();
                    xhr_stop.open("POST", "http://" + hostname + port + "/stop", true, "AirPlay", null);
                    xhr_stop.send(null);                    
                    playback_started = false;
                }

                // playback stopped, AppleTV is "readyToPlay"
                if (playback_started && playback_info_keys_count == 2) {
                    console.log("sending /stop signal, setting playback_started = false")
                    var xhr_stop = new XMLHttpRequest();
                    xhr_stop.open("POST", "http://" + hostname + port + "/stop", true, "AirPlay", null);
                    xhr_stop.send(null);
                    playback_started = false;
                    terminate_loop = true;
                }

            }, false);

            xhr.addEventListener("error", function() {
                clearInterval(timer);
            }, false);
            xhr.send(null);

        }, 5000);

    }, false);

Upvotes: 1

Laurent Perrin
Laurent Perrin

Reputation: 14881

It is not possible to achieve this in JavaScript. However, you might be able to run it from the browser with an NPAPI plugin (with great pain).

If you can run a local server, there are several node.js modules which make this much easier. The following example will stream any audio file that is posted to a nearby AirPlay device.

  • It requires the airtunes module from NPM, which I maintain.
  • It uses FFmpeg to transcode the file.

You can test it with:

curl -X POST --data-binary @sample.mp3 http://localhost:8080/audio

It assumes that FFmpeg is located in /usr/local/bin/ffmpeg, and that an AirPlay device is available on localhost:5000 (you can try with Airfoil Speakers).

var airtunes = require('airtunes'),
    express = require('express'),
    app = express(),
    device = airtunes.add('localhost'),
    spawn = require('child_process').spawn;

app.post('/audio', function(req, res) {
  // use ffmpeg to reencode data on the fly
  var ffmpeg = spawn('/usr/local/bin/ffmpeg', [
    '-i', 'pipe:0',       // Read from stdin
    '-f', 's16le',        // PCM 16bits, little-endian
    '-ar', '44100',       // Sampling rate
    '-ac', 2,             // Stereo
    'pipe:1'              // Output to stdout
  ]);

  // pipe data to AirTunes
  ffmpeg.stdout.pipe(airtunes, { end: false });

  // detect if ffmpeg was not spawned correctly
  ffmpeg.stderr.setEncoding('utf8');
  ffmpeg.stderr.on('data', function(data) {
    if(/^execvp\(\)/.test(data)) {
      console.log('failed to start ' + argv.ffmpeg);
      process.exit(1);
    }
  });

  req.pipe(ffmpeg.stdin);

  req.on('end', function() {
    res.end();
 });
});

device.on('status', function(status) {
  console.log('status: ' + status);
});

console.log('listening on port 8080');
app.listen(8080);

Upvotes: 3

Related Questions