google-castchromecastsmooth-streaming

SmoothStreaming issues on Chromecast


I'm trying to load a smoothstreaming media into the chromecast. For that I've used the samples provided by google:

<body>
<video id='vid' />
<script type="text/javascript"
    src="//www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js">
</script>
<script type="text/javascript"
    src="//www.gstatic.com/cast/sdk/libs/mediaplayer/0.3.0/media_player.js">
</script>

<script type="text/javascript">
window.onload = function() {
// If you set ?Debug=true in the URL, such as a different App ID in the
// developer console, include debugging information.
    cast.receiver.logger.setLevelValue(cast.receiver.LoggerLevel.DEBUG);

  var mediaElement = document.getElementById('vid');

// Create the media manager. This will handle all media messages by default.
  window.mediaManager = new cast.receiver.MediaManager(mediaElement);

// Remember the default value for the Receiver onLoad, so this sample can Play
// non-adaptive media as well.
  window.defaultOnLoad = mediaManager.onLoad;
  mediaManager.onLoad = function (event) {
/proto

// MPEG-DASH
        protocol = cast.player.api.CreateDashStreamingProtocol(host);
      } else if (url.indexOf('.ism/') >= 0) {
// Smooth Streaming
        protocol = cast.player.api.CreateSmoothStreamingProtocol(host);
      }
// How to override a method in Host. I know that it's safe to just provide this
// method.
      host.onError = function(errorCode) {
        console.log("Fatal Error - "+errorCode);
        window.player.unload();
      };
// If you need cookies, then set withCredentials = true also set any header
// information you need.  If you don't need them, there can be some unexpected
// effects by setting this value.
//      host.updateSegmentRequestInfo = function(requestInfo) {
//        requestInfo.withCredentials = true;
//      };
      console.log("we have protocol "+ext);
      if (protocol !== null) {
        console.log("Starting Media Player Library");
        window.player = new cast.player.api.Player(host);
        window.player.load(protocol, initStart);
      }
      mediaElement.autoplay = autoplay;  // Make sure autoplay get's set
      if (url.lastIndexOf('.m3u8') >= 0) {
// HTTP Live Streaming
        protocol = cast.player.api.CreateHlsStreamingProtocol(host);
      } else if (url.lastIndexOf('.mpd') >= 0) {
// MPEG-DASH
        protocol = cast.player.api.CreateDashStreamingProtocol(host);
      } else if (url.indexOf('.ism/') >= 0) {
// Smooth Streaming
        protocol = cast.player.api.CreateSmoothStreamingProtocol(host);
      }
// How to override a method in Host. I know that it's safe to just provide this
// method.
      host.onError = function(errorCode) {
        console.log("Fatal Error - "+errorCode);
        window.player.unload();
      };
// If you need cookies, then set withCredentials = true also set any header
// information you need.  If you don't need them, there can be some unexpected
// effects by setting this value.
//      host.updateSegmentRequestInfo = function(requestInfo) {
//        requestInfo.withCredentials = true;
//      };
      console.log("we have protocol "+ext);

And on the sender:

/**
 * global variables
 */
var currentMediaSession = null;
var currentVolume = 0.5;
var progressFlag = 1;
var mediaCurrentTime = 0;
var session = null;
var mediaURLs = [
           'http://playready.directtaps.net/smoothstreaming/TTLSS720VC1/To_The_Limit_720.ism/',
           'http://commondatastorage.googleapis.com/gtv-videos-bucket/ED_1280.mp4',
           'http://commondatastorage.googleapis.com/gtv-videos-bucket/tears_of_steel_1080p.mov',
           'http://commondatastorage.googleapis.com/gtv-videos-bucket/reel_2012_1280x720.mp4',
           'http://commondatastorage.googleapis.com/gtv-videos-bucket/Google%20IO%202011%2045%20Min%20Walk%20Out.mp3'];
var mediaTitles = [
           'Big Buck Bunny',
           'Elephant Dream',
           'Tears of Steel',
           'Reel 2012',
           'Google I/O 2011 Audio'];

var mediaThumbs = [
           'images/bunny.jpg',
           'images/ed.jpg',
           'images/Tears.jpg',
           'images/reel.jpg',
           'images/google-io-2011.jpg'];
var currentMediaURL = mediaURLs[0];


/**
 * Call initialization
 */
if (!chrome.cast || !chrome.cast.isAvailable) {
  setTimeout(initializeCastApi, 1000);
}

/**
 * initialization
 */
function initializeCastApi() {
  // default app ID to the default media receiver app
  // optional: you may change it to your own app ID/receiver
  var applicationID = '21176C05';
  var sessionRequest = new chrome.cast.SessionRequest(applicationID);
  var apiConfig = new chrome.cast.ApiConfig(sessionRequest,
    sessionListener,
    receiverListener);

  chrome.cast.initialize(apiConfig, onInitSuccess, onError);
};

/**
 * initialization success callback
 */
function onInitSuccess() {
  appendMessage("init success");
}

/**
 * initialization error callback
 */
function onError() {
  console.log("error");
  appendMessage("error");
}

/**
 * generic success callback
 */
function onSuccess(message) {
  console.log(message);
}

/**
 * callback on success for stopping app
 */
function onStopAppSuccess() {
  console.log('Session stopped');
  appendMessage('Session stopped');
  document.getElementById("casticon").src = 'images/cast_icon_idle.png'; 
}

/**
 * session listener during initialization
 */
function sessionListener(e) {
  console.log('New session ID: ' + e.sessionId);
  appendMessage('New session ID:' + e.sessionId);
  session = e;
  if (session.media.length != 0) {
    appendMessage(
        'Found ' + session.media.length + ' existing media sessions.');
    onMediaDiscovered('onRequestSessionSuccess_', session.media[0]);
  }
  session.addMediaListener(
      onMediaDiscovered.bind(this, 'addMediaListener'));
  session.addUpdateListener(sessionUpdateListener.bind(this));  
}

/**
 * session update listener 
 */
function sessionUpdateListener(isAlive) {
  var message = isAlive ? 'Session Updated' : 'Session Removed';
  message += ': ' + session.sessionId;
  appendMessage(message);
  if (!isAlive) {
    session = null;
  }
};

/**
 * receiver listener during initialization
 */
function receiverListener(e) {
  if( e === 'available' ) {
    console.log("receiver found");
    appendMessage("receiver found");
  }
  else {
    console.log("receiver list empty");
    appendMessage("receiver list empty");
  }
}

/**
 * select a media URL 
 * @param {string} m An index for media URL
 */
function selectMedia(m) {
  console.log("media selected" + m);
  appendMessage("media selected" + m);
  currentMediaURL = mediaURLs[m]; 
  var playpauseresume = document.getElementById("playpauseresume");
  document.getElementById('thumb').src = mediaThumbs[m];
}

/**
 * launch app and request session
 */
function launchApp() {
  console.log("launching app...");
  appendMessage("launching app...");
  chrome.cast.requestSession(onRequestSessionSuccess, onLaunchError);
}

/**
 * callback on success for requestSession call  
 * @param {Object} e A non-null new session.
 */
function onRequestSessionSuccess(e) {
  console.log("session success: " + e.sessionId);
  appendMessage("session success: " + e.sessionId);
  session = e;
  document.getElementById("casticon").src = 'images/cast_icon_active.png'; 
}

/**
 * callback on launch error
 */
function onLaunchError() {
  console.log("launch error");
  appendMessage("launch error");
}

/**
 * stop app/session
 */
function stopApp() {
  session.stop(onStopAppSuccess, onError);
}

/**
 * load media
 * @param {string} i An index for media
 */
function loadMedia(i) {
  if (!session) {
    console.log("no session");
    appendMessage("no session");
    return;
  }
  console.log("loading..." + currentMediaURL);
  appendMessage("loading..." + currentMediaURL);
  var mediaInfo = new chrome.cast.media.MediaInfo(currentMediaURL);
  mediaInfo.contentType = 'application/vnd.ms-sstr+xml';
  var request = new chrome.cast.media.LoadRequest(mediaInfo);
  request.autoplay = false;
  request.currentTime = 0;

  var payload = {
    "title:" : mediaTitles[i],
    "thumb" : mediaThumbs[i]
  };

  var json = {
    "payload" : payload
  };

  request.customData = json;

  session.loadMedia(request,
    onMediaDiscovered.bind(this, 'loadMedia'),
    onMediaError);

}

/**
 * callback on success for loading media
 * @param {Object} e A non-null media object
 */
function onMediaDiscovered(how, mediaSession) {
  console.log("new media session ID:" + mediaSession.mediaSessionId);
  appendMessage("new media session ID:" + mediaSession.mediaSessionId + ' (' + how + ')');
  currentMediaSession = mediaSession;
  mediaSession.addUpdateListener(onMediaStatusUpdate);
  mediaCurrentTime = currentMediaSession.currentTime;
  playpauseresume.innerHTML = 'Play';
  document.getElementById("casticon").src = 'images/cast_icon_active.png'; 
}

/**
 * callback on media loading error
 * @param {Object} e A non-null media object
 */
function onMediaError(e) {
  console.log("media error");
  appendMessage("media error");
  document.getElementById("casticon").src = 'images/cast_icon_warning.png'; 
}

/**
 * callback for media status event
 * @param {Object} e A non-null media object
 */
function onMediaStatusUpdate(isAlive) {
  if( progressFlag ) {
    document.getElementById("progress").value = parseInt(100 * currentMediaSession.currentTime / currentMediaSession.media.duration);
  }
  document.getElementById("playerstate").innerHTML = currentMediaSession.playerState;
}

/**
 * play media
 */
function playMedia() {
  if( !currentMediaSession ) 
    return;

  var playpauseresume = document.getElementById("playpauseresume");
  if( playpauseresume.innerHTML == 'Play' ) {
    currentMediaSession.play(null,
      mediaCommandSuccessCallback.bind(this,"playing started for " + currentMediaSession.sessionId),
      onError);
      playpauseresume.innerHTML = 'Pause';
      //currentMediaSession.addListener(onMediaStatusUpdate);
      appendMessage("play started");
  }
  else {
    if( playpauseresume.innerHTML == 'Pause' ) {
      currentMediaSession.pause(null,
        mediaCommandSuccessCallback.bind(this,"paused " + currentMediaSession.sessionId),
        onError);
      playpauseresume.innerHTML = 'Resume';
      appendMessage("paused");
    }
    else {
      if( playpauseresume.innerHTML == 'Resume' ) {
        currentMediaSession.play(null,
          mediaCommandSuccessCallback.bind(this,"resumed " + currentMediaSession.sessionId),
          onError);
        playpauseresume.innerHTML = 'Pause';
        appendMessage("resumed");
      }
    }
  }
}

/**
 * stop media
 */
function stopMedia() {
  if( !currentMediaSession ) 
    return;

  currentMediaSession.stop(null,
    mediaCommandSuccessCallback.bind(this,"stopped " + currentMediaSession.sessionId),
    onError);
  var playpauseresume = document.getElementById("playpauseresume");
  playpauseresume.innerHTML = 'Play';
  appendMessage("media stopped");
}

/**
 * set media volume
 * @param {Number} level A number for volume level
 * @param {Boolean} mute A true/false for mute/unmute 
 */
function setMediaVolume(level, mute) {
  if( !currentMediaSession ) 
    return;

  var volume = new chrome.cast.Volume();
  volume.level = level;
  currentVolume = volume.level;
  volume.muted = mute;
  var request = new chrome.cast.media.VolumeRequest();
  request.volume = volume;
  currentMediaSession.setVolume(request,
    mediaCommandSuccessCallback.bind(this, 'media set-volume done'),
    onError);
}

/**
 * mute media
 * @param {DOM Object} cb A checkbox element
 */
function muteMedia(cb) {
  if( cb.checked == true ) {
    document.getElementById('muteText').innerHTML = 'Unmute media';
    setMediaVolume(currentVolume, true);
    appendMessage("media muted");
  }
  else {
    document.getElementById('muteText').innerHTML = 'Mute media';
    setMediaVolume(currentVolume, false);
    appendMessage("media unmuted");
  } 
}

/**
 * seek media position
 * @param {Number} pos A number to indicate percent
 */
function seekMedia(pos) {
  console.log('Seeking ' + currentMediaSession.sessionId + ':' +
    currentMediaSession.mediaSessionId + ' to ' + pos + "%");
  progressFlag = 0;
  var request = new chrome.cast.media.SeekRequest();
  request.currentTime = pos * currentMediaSession.media.duration / 100;
  currentMediaSession.seek(request,
    onSeekSuccess.bind(this, 'media seek done'),
    onError);
}

/**
 * callback on success for media commands
 * @param {string} info A message string
 * @param {Object} e A non-null media object
 */
function onSeekSuccess(info) {
  console.log(info);
  appendMessage(info);
  setTimeout(function(){progressFlag = 1},1500);
}

/**
 * callback on success for media commands
 * @param {string} info A message string
 * @param {Object} e A non-null media object
 */
function mediaCommandSuccessCallback(info) {
  console.log(info);
  appendMessage(info);
}


/**
 * append message to debug message window
 * @param {string} message A message string
 */
function appendMessage(message) {
  var dw = document.getElementById("debugmessage");
  dw.innerHTML += '\n' + JSON.stringify(message);
};

I am trying media #1 which is .ism - I also tried with the /Manifest - but no luck. In the debugging console I am getting:

[214.054s] [cast.player.api.Player] load -3
 media_player.js:23
 [214.060s] [goog.net.XhrIo] Opening Xhr [GET http://playready.directtaps.net/smoothstreaming/TTLSS720VC1/To_The_Limit_720.ism/Manifest -1]
 media_player.js:23
 [214.065s] [goog.net.XhrIo] Will abort after 10000ms if incomplete, xhr2 false [GET http://playready.directtaps.net/smoothstreaming/TTLSS720VC1/To_The_Limit_720.ism/Manifest -1]
 media_player.js:23
 [214.070s] [goog.net.XhrIo] Sending request [GET http://playready.directtaps.net/smoothstreaming/TTLSS720VC1/To_The_Limit_720.ism/Manifest -1]
 media_player.js:23
 [214.088s] [goog.net.XhrIo] Request complete [GET http://playready.directtaps.net/smoothstreaming/TTLSS720VC1/To_The_Limit_720.ism/Manifest 200]
 media_player.js:23
 [214.120s] [cast.player.api.Player] sourceopen
 media_player.js:23
 [214.142s] [cast.player.api.Player] play
 media_player.js:23
 [214.291s] [cast.receiver.MediaManager] Load metadata error
 cast_receiver.js:19
 [214.296s] [cast.receiver.MediaManager] Resetting media element
 cast_receiver.js:19
 [214.303s] [cast.receiver.MediaManager] Sending error message to 4:client-34217
 cast_receiver.js:19
 [214.307s] [cast.receiver.IpcChannel] IPC message sent: {"namespace":"urn:x-cast:com.google.cast.media","senderId":"4:client-34217","data":"{\"requestId\":9217017,\"type\":\"LOAD_FAILED\"}"}
 cast_receiver.js:19
 [214.171s] [cast.player.api.Player] error
 media_player.js:23
Fatal Error - 1 index.html:61
 [214.176s] [cast.player.api.Player] unload -3

Any idea???

Thanks!


Solution

  • Where did you find that URL? It's incomplete, the right one is http://playready.directtaps.net/smoothstreaming/TTLSS720VC1/To_The_Limit_720.ism/manifest, but it is unlikely to work. Other files have been posted at: Microsoft's SmoothStreaming samples files, that said, I don't believe that Microsoft is providing a CORS header that works for Chromecast.

    We do provide some very simple media that you can host yourself. And we provide a very simple server, if you need that to host and provide a CORS header as well.