Wednesday, October 27, 2010

Resizing Cross-domain Content in an iFrame in YouTube Video Embeds

Saw the post, "HTML5 Video Available on the Web – October Update" by Frank Sinton, which linked to YouTube's beta support for embedding via iFrame. I noticed that the linked YouTube post, "A New Way To Embed YouTube Videos", was in a different domain than the embedded video. So, I looked a little deeper.

I chose a currently popular Monty Python video clip from Life of Brian, and looked at the HTML (as of 2010-10-27):

<!DOCTYPE html>
<html>
<head>
    <title>YouTube - Monty Python's - Life of Brian-5</title>

  <style>
    body {
      font: 12px Arial, sans-serif;
      background-color: #000000;
      color: #FFFFFF;
      height: 100%;
      width: 100%;
      margin: 0;
      overflow: hidden;
    }
  </style>
    
    <script  src="http://s.ytimg.com/yt/jsbin/www-embed-vflqRiLRQ.js"></script>

</head>
<body>
  <div id="watch-longform-ad" style="display:none;">

    <div id="watch-longform-text">
Advertisement
    </div>
    <div id="watch-longform-ad-placeholder"><img src="http://s.ytimg.com/yt/img/pixel-vfl3z5WfW.gif" height="60" width="300" /></div>
  </div>

    <div id="html5-player-css-holder"></div>
  <div id="embed-holder">
    <div id="watch-player-div" class="flash-player" style="position: absolute; width:100%; height:100%;"></div>
      <div id="video-player" class="html5-video-player" tabindex="0">

    <div class="video-fallback" style="display: none;">
      Your browser does not currently recognize any of the video formats available.<br>
      <a href="/html5">Click here to visit our frequently asked questions about HTML5 video.</a>
    </div>
    <div id="captions" class="video-captions"><div class="captions-holder"><span id="captions-text" class="hidden"></span></div></div>
    <div class="video-content">
      <div class="html5-video-loader html5-center-overlay html5-icon"></div>
        <svg class="html5-big-play-button html5-center-overlay">

          <g opacity="0.4">
            <path fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" d="M88.34,9.698
              c-1.09-1.04-2.43-1.62-3.92-1.8c-23.061-1.44-46.08-1.44-69.14,0c-1.48,0.18-2.72,0.76-3.84,1.8c-1.09,1.04-1.81,2.27-2.14,3.78
              c-1.05,7.07-1.56,14.17-1.56,21.26c0,7.21,0.51,14.27,1.56,21.34c0.33,1.51,1.05,2.74,2.14,3.779c1.12,1.051,2.36,1.621,3.84,1.801
              c23.06,1.439,46.08,1.439,69.14,0c1.49-0.18,2.83-0.75,3.92-1.801c1.12-1.039,1.77-2.34,2.1-3.819
              C91.46,48.938,92,41.878,92,34.738c0-7.09-0.54-14.16-1.561-21.22C90.109,12.038,89.46,10.818,88.34,9.698z"></path>
          </g>
          <g class="html5-overlay-button-background" opacity="0.8">
            <path d="M88.34,9.698c-1.09-1.04-2.43-1.62-3.92-1.8c-23.061-1.44-46.08-1.44-69.14,0c-1.48,0.18-2.72,0.76-3.84,1.8
              c-1.09,1.04-1.81,2.27-2.14,3.78c-1.05,7.07-1.56,14.17-1.56,21.26c0,7.21,0.51,14.27,1.56,21.34c0.33,1.51,1.05,2.74,2.14,3.779
              c1.12,1.051,2.36,1.621,3.84,1.801c23.06,1.439,46.08,1.439,69.14,0c1.49-0.18,2.83-0.75,3.92-1.801
              c1.12-1.039,1.77-2.34,2.1-3.819C91.46,48.938,92,41.878,92,34.738c0-7.09-0.54-14.16-1.561-21.22
              C90.109,12.038,89.46,10.818,88.34,9.698z"></path>
          </g>
          <path opacity="0.19" fill="#FFFFFF" enable-background="new" d="M88.34,9.698c-1.09-1.04-2.43-1.62-3.92-1.8
            c-23.061-1.44-46.08-1.44-69.14,0c-1.48,0.18-2.72,0.76-3.84,1.8c-1.09,1.04-1.81,2.27-2.14,3.78c-1.05,7.07-1.56,14.17-1.56,21.26
            c0,7.21,1.49,21.949,1.56,21.34c30.89-29.62,44.3-5.48,81.14-42.56C91.51,12.438,89.46,10.818,88.34,9.698z"></path>
          <g class="html5-overlay-button-background" opacity="0.66">
            <polygon fill="#FFFFFF" points="39.32,16.729 39.32,52.798 68.18,34.168"></polygon>

          </g>
        </svg>
      <ul class="html5-context-menu yt-uix-button-menu hid">
        <li>
          <a class="yt-uix-button-menu-item" target="_blank" href="http://www.youtube.com/watch?v=dQw4w9WgXcQ">Save Video As...</a>
        </li>
        <li>
          <a class="yt-uix-button-menu-item" href="/html5">About HTML5</a>

        </li>
      </ul>
    </div>
    <div class="video-controls" style="display: none;">
      <img class="html5-watermark html5-stop-propagation html5-icon" src="http://s.ytimg.com/yt/img/pixel-vfl3z5WfW.gif" alt="watermark">
      <div class="html5-progress-bar html5-stop-propagation">
        <div class="html5-scrubber-button html5-progress-item html5-icon"></div>
        <div class="html5-progress-list html5-progress-item">
          <div class="html5-play-progress html5-progress-section"></div>

          <div class="html5-load-progress html5-progress-section"></div>
        </div>
      </div>
      <div class="html5-player-chrome html5-stop-propagation">
        <div class="html5-play-button html5-button html5-control">
          <input type="image" class="html5-icon" src="http://s.ytimg.com/yt/img/pixel-vfl3z5WfW.gif" alt="Play/Pause">
        </div>
        <div class="html5-volume-control html5-control yt-uix-tooltip" tabindex="0" title="Mute" data-alt-title="Unmute">
          <div class="html5-volume-button html5-button" data-value="loud">

            <input type="image" class="html5-icon" src="http://s.ytimg.com/yt/img/pixel-vfl3z5WfW.gif" tabindex="-1">
          </div>
          <div class="html5-volume-panel">
            <div class="html5-volume-slider html5-icon"></div>
          </div>
        </div>
        <div class="progress-text html5-control">
          <span class="current-time">00:00</span><span> / </span><span class="duration-time">00:00</span>

        </div>

        <div class="html5-fullscreen-button html5-button html5-control html5-control-right yt-uix-tooltip" data-value="fullscreen" title="Full screen" data-alt-title="Exit full screen">
          <input type="image" class="html5-icon" src="http://s.ytimg.com/yt/img/pixel-vfl3z5WfW.gif" alt="Full screen">
        </div>
        <div class="html5-captions-button html5-button-popup-menu html5-control html5-control-right">
          <span class="html5-button-label html5-empty-label">
            <input type="image" class="html5-icon" src="http://s.ytimg.com/yt/img/pixel-vfl3z5WfW.gif" alt="Subtitles">
          </span>

        </div>
        <div class="html5-quality-button html5-button-popup-menu html5-control html5-control-right" title="Quality">
          <span class="html5-button-label">360p</span>
          <ul class="html5-quality-popup-menu html5-popup-menu">
            <li class="html5-popup-menu-item" data-value="1080p">
              1080p <sup>HD</sup>
            </li>

            <li class="html5-popup-menu-item" data-value="720p">
              720p <sup>HD</sup>
            </li>
            <li class="html5-popup-menu-item" data-value="480p">
              480p
            </li>
            <li class="html5-popup-menu-item" data-value="360p">
              360p
            </li>

          </ul>
        </div>
        <div class="html5-speed-button html5-button-popup-menu html5-control html5-control-right" title="Speed">
          <span class="html5-button-label">Normal</span>
          <ul class="html5-speed-popup-menu html5-popup-menu">
            <li class="html5-popup-menu-item" data-value="2.0">
2x Speed
            </li>
            <li class="html5-popup-menu-item" data-value="1.5">

1.5x Speed
            </li>
            <li class="html5-popup-menu-item" data-value="1.0">
Normal Speed
            </li>
            <li class="html5-popup-menu-item" data-value="0.5">
&frac12; Speed
            </li>
            <li class="html5-popup-menu-item" data-value="0.25">
&frac14; Speed
            </li>
          </ul>

        </div>
        <a class="html5-control html5-control-right html5-control-last html5-player-branding" href="/html5">
          HTML5
        </a>
      </div>
    </div>
  </div>

  </div>

  <script type="text/javascript">
  var playerElementId = "";

  var writeFlashEmbed = function() {
    var video_url = "http://www.youtube.com/v/ZNeq2Utm0nU?showinfo=0&amp;enablejsapi=1&amp;version=3&amp;et=OEgsToPDskKqrAsC70ymD-QZmm4A977Q&amp;iurl=http%3A%2F%2Fi3.ytimg.com%2Fvi%2FZNeq2Utm0nU%2Fhqdefault.jpg&amp;el=embedded&amp;use_native_controls=False&amp;video_id=ZNeq2Utm0nU&amp;autohide=0&amp;hl=en_US&amp;eurl=&amp;autoplay=0";
    var fo = new SWFObject(video_url, "movie_player", "100%", "100%", "7", "#000000");
    var startTime = yt.www.watch.player.processLocationHashSeekTime();
    if (window.opener
      && window.opener.yt
      && window.opener.yt.getConfig
      && window.opener.yt.getConfig('SEQUENTIAL_VIDEO_LIST')) {
      fo.addVariable("playlist", window.opener.yt.getConfig('SEQUENTIAL_VIDEO_LIST'));
    }
    fo.addParam("allowFullscreen", "true");
    fo.addParam("AllowScriptAccess", "always");

    if (startTime) {
      fo.addVariable('start', startTime);
    }
    fo.write("watch-player-div");

    playerElementId = "movie_player";

      handleResize = function() {
    var windowHeight = window.innerHeight;
    var adjustedHeight = windowHeight - _gel('watch-longform-ad').offsetHeight;
    var percentHeight = Math.round((adjustedHeight * 100) / windowHeight) + "%";
    _gel('watch-player-div').style.height = percentHeight;
  }
  yt.events.listen(window, 'resize', handleResize);
  yt.events.listen(_gel('watch-longform-ad-placeholder'), 'resize', handleResize);

  };

  var writeHtml5Embed = function(availableFormats) {
    document.getElementById("html5-player-css-holder").innerHTML = "  <link  rel=\"stylesheet\" href=\"http:\/\/s.ytimg.com\/yt\/cssbin\/www-player-vflbyieqp.css\">\n";

    var startTime = yt.www.watch.player.processLocationHashSeekTime();

    var videoPlayer = new yt.player.VideoPlayer();
    videoPlayer.setAvailableFormats(availableFormats);

    var playerVars = {"iurl": "http:\/\/i3.ytimg.com\/vi\/ZNeq2Utm0nU\/hqdefault.jpg", "el": "embedded", "use_native_controls": false, "video_id": "ZNeq2Utm0nU", "autohide": "0", "hl": "en_US", "eurl": "", "autoplay": "0"};
    if (startTime) {
      playerVars['start'] = startTime;
    }
    videoPlayer.setTargetElementId("video-player");
    videoPlayer.setVideoData(playerVars);
    videoPlayer.initialize(startTime, true, true);

    // js api dispatcher
    // Only works now for void fns
    // TODO: figure out how to do callbacks synchronously, and have arguments, probably with JSON

    playerElementId = "video-player";
      handleResize = function() {
    var windowHeight = window.innerHeight;
    var adjustedHeight = windowHeight - _gel('watch-longform-ad').offsetHeight;
    var percentHeight = Math.round((adjustedHeight * 100) / windowHeight) + "%";
    _gel('video-player').style.height = percentHeight;
  }
  yt.events.listen(window, 'resize', handleResize);
  yt.events.listen(_gel('watch-longform-ad-placeholder'), 'resize', handleResize);

  }

  var availableFormats = yt.player.VideoFormat.formatListFromMap(
      []);

  var supportsHtml5 = yt.player.VideoFormat.hasSupportedFormats(availableFormats);

  if (supportsHtml5) {
    writeHtml5Embed(availableFormats);
  } else {
    _gel('embed-holder').removeChild(_gel('video-player'));
    writeFlashEmbed();
  }

  var playerElement = document.getElementById(playerElementId);

  //add JS API for iframe
  var whitelistApiCalls = {
    'playVideo': true,
    'pauseVideo': true,
    'seekTo': true,
    'mute': true,
    'unMute': true};
  var receiveYtMessage = function(event) {
    var message = event.data || (event.J && event.J.data);
    if (typeof(message) == 'string') {
      // Chrome can handle message data being a non-string
      // object, but Firefox can't; best to always use JSON
      message = goog.json.parse(message);
    }
    var fnName = message && message['f'];
    var argList = (message && message['a']) || [];
    if (whitelistApiCalls[fnName] && playerElement[fnName]) {
      var fn = playerElement[fnName];
      var result;
      if (fn) {
        result = fn.apply(playerElement, argList);
      }
    }
    // TODO(libra): return result?

    // TODO: figure out how to do callbacks synchronously
    //event.source.postMessage("1234.567", event.origin);
  };
  yt.events.listen(window, 'message', receiveYtMessage);


  </script>


</body>
</html>

Although the developer(s) looked to be having the same cross-domain communication issues that others have combatted before using anchors (see my previous post- at least I think that is what those comments are about- I've not looked carefully), it is great that there is a neat solution in code for resizing the content within the iFrame (to do full-screen).

No comments: