Simple SVG portal with SMIL and aspect ratio

This page will display a scaled TV channel on screen and let you flip through channels. Play, pause, and channel change is handled using either SVG xlink:href or SMIL method calls. This example also uses TOI to listen to events from the mediaplayer. Both the .svg and the .js files are needed.

svg_smil_sample.svg

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg onload="onLoad()"
     onunload="onUnload()"
     id="root" version="1.2"
     width="100%" height="100%"
     top="0" left="0"
     position="fixed"
     viewBox="0 0 1280 720"
     preserveAspectRatio="none"
     viewport-fill="#C0C0C0"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     xmlns:ekioh="http://www.ekioh.com/2007/ekioh"
     cursor="no"
     focusable="true"
 >
  <foreignObject id="toiJs2Plugin"
                    requiredExtensions="application/x-motorola-toi"
                    hidden="true"/>

  <g id="texts">
     <text x="100" y="100">position=</text>
     <text x="100" y="120" id="player_pos">-</text>

     <text x="200" y="100">pace=</text>
     <text x="200" y="120" id="player_pace">-</text>
     <text x="300" y="100">keycode=</text>
     <text x="300" y="120" id="keycode">-</text>
     <text x="400" y="100">channel=</text>
     <text x="400" y="120" id="channel">-</text>
     <text x="500" y="100">Aspect=</text>
     <text x="500" y="120" id="aspect">-</text>

     <text x="230" y="150">URL=</text>
     <text x="330" y="150" id="videourl">URL</text>
     <text x="230" y="170">Resolution=</text>
     <text x="330" y="170" id="videoinfo">VIDEOINFO</text>
  </g>

  <video id='videoplane'
            x="224px" y="192px" width="608px" height="342px"
            preserveAspectRatio="xMidYMid"
            ekioh:colourKey="#ff000000"
            >
  </video>

  <g id="usage">
     <text x="220" y="600" font-size="16" font-weight="bold" >Usage</text>

     <text x="250" y="620" fill="red">Red: Window size</text>
     <text x="370" y="620" fill="green">Green: Aspect r.</text>
     <text x="490" y="620" fill="yellow">Yellow: Fullscreen</text>
     <text x="620" y="620" fill="blue">Blue: Pause/Play</text>

     <text x="250" y="640">Up/Down: Change channel</text>
  </g>

  <script type="text/ecmascript" xlink:href="svg_smil_sample.js"></script>
</svg>

svg_smil_sample.js

var gSVGNS = "http://www.w3.org/2000/svg";
var gXLINKNS = "http://www.w3.org/1999/xlink";
var REGISTER_CALLBACKS = 1;

var mediaPlayer = null;
var video = null;
var groupTexts = null;
var groupUsage = null;

var currentPace = 1000;
var currAspectWidth = 16;
var channels = [
                "udp://224.5.5.1:11111", // bbc
                "udp://224.5.80.4:10000",
                "udp://224.5.80.6:10000",
                "udp://224.5.80.2:10000",
                "udp://224.5.5.1:11111",
                "udp://224.5.80.1:10000", // disc hd
                "udp://224.5.5.2:11111",
                "udp://224.5.80.5:10000" // tv6
                ];

var nrChannels = channels.length;
var currChan = 1;
var flagFullscreen = 0;
var flagAspectRatio = 0;

var states = new Array("STATE_IDLE","STATE_CONNECTING","STATE_PLAYING","STATE_PAUSED","STATE_FAILED");
var reasons = new Array("REASON_COMMAND_COMPLETED","REASON_COMMAND_NOT_SUPPORTED","REASON_POSITION_EDGE",
        "REASON_ERROR_SERVER_UNAVAILABLE","REASON_ERROR_UNAUTHORIZED","REASON_ERROR_NOT_FOUND",
        "REASON_ERROR_NOT_ENOUGH_BANDWIDTH","REASON_ERROR_OTHER");

function onLoad() {
 /*
  * This line ensures that WebKit has completed page layout etc. before the rest of
  * the function is executed. If this is not done, the plugin will not be
  * available when called.
  */
  document.addEventListener("keydown", onKeyDown, false);

  try {
    mediaPlayer = toi.mediaService.createPlayerInstance();

    if (REGISTER_CALLBACKS != 0) {
      mediaPlayer.addEventListener(mediaPlayer.ON_STATE_CHANGED, onStateChanged);
      mediaPlayer.addEventListener(mediaPlayer.ON_POSITION_CHANGED, onPositionChanged);
      mediaPlayer.addEventListener(mediaPlayer.ON_STREAM_INFO_CHANGED,
                                   onStreamInfoChanged);
    }
  } catch(e) {
    alert("Failed creating player: " + e);
  }

  document.getElementById("keycode").textContent = "unset";
  video = document.getElementById("videoplane");
  groupTexts = document.getElementById("texts");
  groupUsage = document.getElementById("usage");

  startPlaying();
  toggleAspectRatio();
}


function onUnload() {
  try {
    if (mediaPlayer.getState() > 0) {
      mediaPlayer.close();
      alert("Closed media player.");
    }
    if (REGISTER_CALLBACKS != 0) {
        mediaPlayer.removeEventListener(mediaPlayer.ON_STATE_CHANGED, onStateChanged);
        mediaPlayer.removeEventListener(mediaPlayer.ON_POSITION_CHANGED, onPositionChanged);

        mediaPlayer.removeEventListener(mediaPlayer.ON_STREAM_INFO_CHANGED,
                                        onStreamInfoChanged);
    }

    mediaPlayer.releaseInstance();
    mediaPlayer = null;

    document.removeEventListener("keydown", onKeyDown, false);
  } catch(e) {
    alert("Onunload error:" + e);
  }
}


function startPlaying() {
  var url = channels[currChan];
  video.endElement();
  document.getElementById("channel").textContent = currChan;
  alert("startPlaying: " + url);
  document.getElementById("videourl").textContent = url;
  video.setAttributeNS(gXLINKNS, "href", url);
  video.beginElement();
  currentPace = 1000;
}

function setPace(npace) {
  if (currentPace == 0 && npace == 1000) {
    video.resumeElement();
  } else if (npace == 0) {
    video.pauseElement();
  } else {
    alert("setPace: timed element does not support pace=" + npace);
    return;
  }
  currentPace = npace;
  document.getElementById("player_pace").textContent =  currentPace;
}

function checkValidUpperChannel() {
  if (currChan >= nrChannels) {
    currChan = 0;
  }
}

function onKeyDown(ev) {
  var key = ev.keyIdentifier;
  alert("onKeyDown " + key);
  document.getElementById("keycode").textContent = key;

  if (key == "Up") {
    currChan = currChan + 1;
    startPlaying();
  } else if (key == "Down") {
    currChan = currChan - 1;
    if (currChan <= 0) {
      currChan = nrChannels - 1;
    }
    startPlaying();
  } else if (key == "Red") {
    toggleWindowsize(1);
  } else if (key == "Green") {
    toggleAspectRatio();
  } else if (key == "Yellow") {
    flagFullscreen = 1 - flagFullscreen;
    setScreenWindow();
  } else if (key == "Blue") {
    var newPace = 1000 - currentPace;
    setPace(newPace);
  } else if (key == "U+0031") {
    currChan = 1;
    checkValidUpperChannel();

    startPlaying();
    flagFullscreen = 1;
    setScreenWindow();
  } else {
    alert("onKeyDown " + ev.keyIdentifier);
  }
}

function onStateChanged(ev) {
  try {
    alert("Media player state changed: state=" +
          states[ev.state] + ", reason=" +
          reasons[ev.reason] + ", code=" +
          ev.code);
  } catch(e) {
    alert("onPositionChanged problem: " + e);
  }
}

function onPositionChanged(ev) {
  try {
    var pos = ev.position;
    var pace = ev.pace;
    document.getElementById("player_pos").textContent = pos;
    document.getElementById("player_pace").textContent = pace;
  }
  catch(e) {
    alert("onPositionChanged problem: " + e);
  }
}

function onStreamInfoChanged() {
 if (mediaPlayer != null) {
    try {
      var si = mediaPlayer.getStreamInfo();
      var videostreaminfo = null;
      var j = 0;
      var text = "(no info)";
      for (j = 0; j < si.availableComponents.length; j++) {
        if (si.availableComponents[j].type == mediaPlayer.COMPONENT_VIDEO) {
          videostreaminfo = mediaPlayer.getVideoStreamInfo(si.availableComponents[j]);
          if (videostreaminfo != null) {
            text = videostreaminfo.resolutionX+" x "+videostreaminfo.resolutionY;
          } else {
            text = "Null object";
          }
        }
      }
      document.getElementById("videoinfo").textContent = text;
      alert("onStreamInfoChanged " + text);
    } catch(e) {
      alert("onStreamInfoChanged problem: " + e);
    }
  }
}

function setScreenWindow() {
  if (flagFullscreen == 1) {
    video.setAttribute("x", 0);
    video.setAttribute("y", 0);
    video.setAttribute("width", 1280);
    video.setAttribute("height", 720);
    groupUsage.setAttribute("visibility", 'hidden');
  } else {
    video.setAttribute("x", 224);
    video.setAttribute("y", 192);
    toggleWindowsize(0);
    groupUsage.setAttribute("visibility", 'visible');
  }
}

function toggleAspectRatio() {
  flagAspectRatio = 1 - flagAspectRatio;
  var aspectText = null;
  if (flagAspectRatio == 0) {
    video.setAttribute("preserveAspectRatio","none");
    aspectText = "strect";
  } else {
    video.setAttribute("preserveAspectRatio","xMidYMid");
    aspectText = "preserve aspect";
  }
  document.getElementById("aspect").textContent = aspectText;
}

function toggleWindowsize(changeAspect) {
  if (flagFullscreen == 1) {
    return;
  }
  if (changeAspect != 0) {
    if (currAspectWidth == 12) {
      currAspectWidth = 16;
    } else {
      currAspectWidth = 12;
    }
  }
  if (currAspectWidth == 12) {
    video.setAttribute("width", 515);
    video.setAttribute("height", 384);
  } else {
    video.setAttribute("width", 608);
    video.setAttribute("height", 342);

  }
}