Corey Mwamba

testbed → SVG audio player...

SVG audio player - a quick walkthrough

I made an SVG player widget around about March time, before I knew about HTML5 audio scripting. It works quite well: Opera users get a nice SVG button; Firefox and Webkit users get a standard HTML5 player. It suits my purposes well and avoids using Flash, which on Linux systems can be a bit of a problem...

On my site it looks like this: . If images are missing, then the HTML5 player will show and thus the sound can still be accessed.

Advantages

  1. the file location isn't immediately easy to find; by using variables you can secure the sound file. If you don't include the HTML5 player then to most casual users the file can only be played. I haven't made any efforts to secure the files as I'm giving the music away, but it is easily achieved;
  2. SVG Tiny 1.2 is audio format agnostic: the specification has no preference in terms of file format, which means that you should be able to use MP3, Ogg Vorbis, FLAC, WAV, AIFF, AMR - whatever you like, as long as your browser/device is capable of playing it;
  3. Most modern mobile devices support SVG Tiny, so it's immediately portable;
  4. You can design how you want it to look by drawing it, and then saving the drawing as SVG - meaning that you could design in OpenOffice.org/Libre Office, Inkscape, on-line at SVG-Edit, and so on... you're not limited to what you use to create it, as long as you can draw;
  5. SVG is text-based. The files are for the player itself are quite small. The smallest Flash-based player I know is Dewplayer which weighs in at 5.78KB. But it only plays MP3s and has its own look. The player is 2.59KB and the animation script is 1.24KB making 3.83KB, for which I could use a wider range of formats and has the design I want; and
  6. you have the files yourself - you're not reliant on an external service, the life-span of which you can never be sure.

Disadvantages

  1. As much as the browser vendors might like to talk about supporting standards there is currently only one browser in which this works fully - Opera. Most other browsers will display the player but not play any sound. I can only hope that audio makes into SVG2;
  2. even though SVG-T is format agnostic, browser vendors are not and Opera has now made the decision to only support Ogg Vorbis. You'd think that since I'm a Linux guy that wouldn't bother me, but in terms of adoption I think it's a retrograde step, and it stops the player being useful on mobile devices, which typically only play MP3 and AMR.

The scripts

  1. anim.js

    My knowledge of Javascript is woeful, so there's no way I could have written this on my own. I used the examples on declarative animation at SVG-Whiz, specifically this animation; and quite a few other places that are lost to me.

    var SVGDocument = null;
    var SVGRoot = null;
    var pauseButton = null;
    var playButton = null;
    var countBar = null;
    var audioFile = null;
    
    function Init(evt) {
     	      var svgns = "http://www.w3.org/2000/svg";
                  var xlinkns = "http://www.w3.org/1999/xlink";
                  SVGDocument = evt.target.ownerDocument;
                  SVGRoot = SVGDocument.documentElement;
                  pauseButton = SVGDocument.getElementById("pauseGroup");
                  playButton = SVGDocument.getElementById("playGroup");
                  countBar = SVGDocument.getElementById("bar");
                  audioFile = SVGDocument.getElementById("sound");
                  SVGRoot.pauseAnimations();
    
                       };
    
    function pause() {
                  SVGRoot.pauseAnimations();
                  pauseButton.setAttributeNS(null, "display", "none");
                  playButton.setAttributeNS(null, "display", "inline");
    		   };
    		 	   
    function play() {
                  playButton.setAttributeNS(null, "display", "none");
    	      pauseButton.setAttributeNS(null, "display", "inline");
                  audioFile.setAttributeNS(null, "begin", "0");
                  countBar.setAttributeNS(null, "begin", "0");
                  SVGRoot.unpauseAnimations();
                       };
    
    function reset() {
                  SVGRoot.setCurrentTime(0);
                  audioFile.setAttributeNS(null, "begin", "play.click");
                  countBar.setAttributeNS(null, "begin", "sound.begin");
                  playButton.setAttributeNS(null, "display", "inline");
    	      pauseButton.setAttributeNS(null, "display", "none");
                  SVGRoot.pauseAnimations();
                       };
  2. switch.svg.php

    Using a PHP file we can handle variables, in this case

    • pth says whether the file is local (l) or external (x). Bear in mind that you may not be able to use external sound files if the server has hot-linking disabled...
    • nam is the full path or name of the file you want to play, but without the extension
    • typ is the format of the file (ogg, mp3, etc.). Since Opera on Linux uses GStreamer, it will play whatever GStreamer can play; and SVG audio is format-agnostic so you're not limited; with HTML5 audio you will have to do some kind of file switching.
    • tim is the length that you want the sound to play for in seconds.

    I'll go through this bit in sequence. First set up the PHP file to be read as an SVG file:

    <?php
    header("Content-type: image/svg+xml");
    echo '<?xml version="1.0" standalone="no"?>';
    ?>

    Then start the SVG file and add a script link to anim.js

    <svg version="1.2" baseProfile="tiny" xml:id="svg-root" 
    width="36" height="36" 
    xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink" 
    xmlns:xe="http://www.w3.org/2001/xml-events" 
    onload="Init(evt)">
    
    <script type="text/javascript" xlink:href="../widgets/anim.js" />

    I drew the design for my player in SVGEdit - so the code for the gradient and circles were defined already: but I wrapped everything up in a group called player.

    <defs>
      <linearGradient y2="0.38672" x2="0.73828" y1="0.38672" x1="0.27734" id="colc">
       <stop stop-opacity="1" stop-color="#007f3f" offset="0" />
       <stop stop-opacity="1" stop-color="#00bfbf" offset="1" />
      </linearGradient>
     </defs>
    
    <g id="player" transform="scale(0.2,0.2) translate(12,8)">
    
          <circle fill-opacity="0" stroke-width="10" stroke="url(#colc)" fill="#ffffff" id="progressbar" r="72.89719" cy="82.49263" cx="77.89719" />

    The marker circle (i.e. the bit that moves around the circle) must be animated when the sound is started: audio is a subset of animation in SVG so they both go inside marker and the variables are fed into the audio element.

    <circle fill-opacity="0" stroke-dasharray="null" stroke-width="5" stroke="#5656ff" fill="#ffffff" id="marker" r="8" cy="9.7" cx="76.89719">
    
                <?php
                   $tim = $_GET['tim'];
                   $nam = $_GET['nam'];
                   $res = $_GET['pth'];
                   $typ = $_GET['typ'];
                   switch ($res) {
                              case "l":
                              $ppath = '../local/path/';
                              break;
    
                              case "x":
                              $ppath = '';
                              break;
                              }
                   echo '<audio id="sound" begin="barmove.begin" 
                   xlink:href="'.$ppath.''.$nam.'.'.$typ.'" 
                   dur="'.$tim.'s" onend="reset()" />'."\n";
    
                   echo '<animateTransform attributeName="transform" 
                   xml:id="barmove" begin="play.focusin"
                   values="0 77.89719 82.49263;360 77.89719 82.49263" 
                   calcMode="linear" type="rotate" dur="'.$tim.'s" 
                   fill="remove" />'."\n";
                 ?>
    </circle>

    I also drew the controls: these are grouped and linked to the play() and pause() functions in our JavaScript.

    <g id="controls">
    <a id="playGroup" display="inline" onactivate="play()" onclick="play()" onfocus="play()">
     <path stroke-dasharray="null" stroke-width="16" stroke="#5fbf00" fill-opacity="0" fill="#5fbf00" id="play" d="m58.89719,46.49263l46,34l-45,34" />
    </a>
    
    <a id="pauseGroup" display="none" onactivate="pause()" onclick="pause()" onfocus="pause()" transform="translate(-3.0)">
       <rect stroke-dasharray="null" stroke-width="11" stroke="#ff0000" fill="#ff0000" id="pauseout" height="49.65116" width="55" y="57.66705" x="53.39719" />
    </a>
    </g>
    
    </g>
    </svg>

The PHP file is then placed in an HTML page as an object:

<object data="../widgets/switch.svg.php?tim=60&nam=fileonserver&pth=l&typ=flac" type="image/svg+xml">...</object>
<object data="../widgets/switch.svg.php?tim=60&nam=http://www.example.com/externalfile&pth=x&typ=ogg" type="image/svg+xml">...</object>