Tuesday, March 29, 2016

Using Dart (dartlang) as an HTML client and Go (golang) as a server

Hi all! I'm back again. This blog might be short on explanation, but basically it gives an example of mixing Google technologies. What this example does is serve MP3 files from the server to the client, which get played on the client's speakers.

First, the server. For that, you'll need Go (https://golang.org/) and a folder to put the Go code and the MP3 files. The only Go file you need is main.go, as follows:

package main      

import (
    "log"
    "net/http"
    "strings"
//    "io/ioutil"
)

func main() {
    port := "localhost:8000"
    log.Println("Opening port", port)
    http.HandleFunc("/music/", getMusic)
    http.ListenAndServe(port, nil)
    }
    


func getMusic(w http.ResponseWriter, r *http.Request)  {
    w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
    w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE")
    url := r.URL.Path
    urlParts := strings.Split(url, "/")
    log.Println(urlParts[2])
    fileName := urlParts[2] + ".mp3"
    log.Println("Sending file", fileName)
    http.ServeFile(w, r, fileName)
      
}

What this code does is listen for a REST "get" request for "localhost:8000/music/". The ending "/" is needed so that the sender can hitch the file name to the URL. The code attaches CORS headers to the response, as this is a cross-origin situation. The URL gets parsed, then the resulting file name gets ".mp3" attached. That file then gets send back to the client with the headers.

The client is a bit more complex. For the client, you need a pubspec.yaml file, a /web subfolder, and Dart (https://www.dartlang.org/).

Your pubspec.yaml should look like:

name: DartMusicPlayer2
author: Ross Albertson <magnusthegood@yahoo.com>
description: A Dart music player using classes

dependencies:
  browser: any
  dart_to_js_script_rewriter: any
  
transformers:
- dart_to_js_script_rewriter   

Use any project name and description you wish, and replace the author with your name and e-mail address.  You'll need the dependencies and transformers to translate your Dart and HTML correctly.

The HTML (/web/index.html) is pretty trivial:

<!DOCTYPE html>
<html>
    <head>
        <title>Dart/Go Music Player 2</title>
        <script type="application/dart" src="main.dart"></script>
        <script src="packages/browser/dart.js"></script>
    </head>
    <body>
        <p>"July" by Weekend <button id="july">Play/Stop</button></p>
        <p>"Drive Blind" by Ride <button id="drive-blind">Play/Stop</button></p>
    </body>
</html> 

The <button> id's and the plain text can be substituted by any mp3 song you have. Lastly, there is the /web/main.dart, as follows:

import 'dart:html';
import 'dart:web_audio';

AudioContext audioContext;

void main() {
  audioContext = new AudioContext();
  new Song(querySelector("#july") as ButtonElement, "July");
  new Song(querySelector("#drive-blind") as ButtonElement, "Drive_Blind");
}

class Song {
  ButtonElement button;
  bool _playing = false;
  // AudioContext _audioContext;
  AudioBufferSourceNode _source;
  String title;

  Song(this.button, this.title) {
    // _audioContext = new AudioContext();

    button..onClick.listen((e) => _toggle());
  }

  _toggle() {
    _playing = !_playing;
    _playing ? _start() : _stop();
  }

  _start() {
    return HttpRequest
        .request("http://localhost:8000/music/$title",
            responseType: "arraybuffer")
        .then((HttpRequest httpRequest) {
      return audioContext
          .decodeAudioData(httpRequest.response)
          .then((AudioBuffer buffer) {
        _source = audioContext.createBufferSource();
        _source.buffer = buffer;
        _source.connectNode(audioContext.destination);
        _source.start(0);
      });
    });
  }

  _stop() {
    _source.stop(0);
  }
}

A global AudioContext is needed in any event. The Song class just requires a <button> element and a file name, minus the file extension. If you want to use multiple file formats, just change the Go so that is uses the URL variable as is, and send the whole file name to the Song constructor. I combined several online Dart examples to come up with this code. The "decodeAudioData" is needed to make the client work with more browsers. This object-oriented approach allows you to put your songs in separate threads. It is also needed to be able to play music after stopping a song; I discovered that quirk in an earlier attempt. You might need to compile the Dart in "debug" mode (pub build --mode debug) and use those /build/web files in production. I found this necessary when using IIS. One last thing... make sure your Dart HTTP request matches with your Go URL and port number.