Embedded YouTube Videos and iPad Rotation

While working on the iPhone/iPad application for the ChickenChannel website, I needed to have the embedded YouTube video resize dynamically to fit properly and in proportion across the screen of the device.  Moreover, the iPad allows for rotating the view, and we needed the video to resize to accomodate both orientations dynamically as you rotate the device.

The Chicken Channel is written in Umbraco, and the iOS application takes advantage of that by presenting existing pages with a customised template.  So while you might look at a recipe on the website and see the nice embedded Youtube video (hidden behind a banner image that prompts you to click it to start playing the video - this was covered in a post last November here.), the iPhone and iPad views are somewhat different.

Step 1: Make the video automatically fill the width of the screen.

This was quite easy:  all I needed to do was clear the width and height the enclosing div and the object tags and set the width to 100% on the embed tag.  I could have set the height as well, but given that there are 3 possible width with the devices, and I'm "veiling" the page until it's loaded anyway, I didn't see any point.  This displays a rather wide but short video on the iPhone:

<div class="youTubePlayer">
  <object class="youTubePlayer">
    <param name="movie" value="http://www.youtube.com/v/rldN0jSBbZQ?fs=1&rel=0" />
    <param name="allowFullScreen" value="true" />
    <param name="allowscriptaccess" value="always" />
    <embed class="youTubePlayer" src="http://www.youtube.com/v/rldN0jSBbZQ?fs=1&rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="100%" />
  </object>
</div>



Step 2: Use Javascript to add the aspect ratio back in (set the Height).

Using jQuery this is a really simple excercise:  We simply get the width of the window, and, because all our videos are in 16:9 aspect ratio, we use that to derive the height before applying it to the relevant tags.  (Notice the embed and object tags also have the class="youTubePlayer" attribute above?) - you'll also need to add the jQuery core library to your page...

  <script type="text/javascript">
    $(document).ready(function() {
      var newHeight = $(window).width()*9/16;
      if (newHeight > 500)
          newHeight = 500;
      $('.youTubePlayer').attr("height", newHeight);
    });
  </script>      



Right, after testing a little, we notice that the page renders, then the video gets lengthened to the correct ratio and all is good in the world.  However, when rotating the iPad application, the videos dimensions aren't resized along with the rest of the content.  Actually, the video's width is resized, but the height stays where we left it.

Step 3: Use the Resize event to adjust the aspect ratio on Rotation

The final step to this process was to take advantage of the UIWebView's resize javascript event to perform the resize again:

  <script type="text/javascript">
    $(window).resize(function() {
      var newHeight = $(window).width()*9/16;
      var oldHeight = $('div.youTubePlayer:first').attr("height");
      if (newHeight > 500)
          newHeight = 500;
      if (newHeight = oldHeight)
          return;
      $('.youTubePlayer').attr("height", newHeight);
    });
</script>



After a little more testing (ie, me madly waving the iPad around in the air and doing acrobatic contortions in the process) we have established that the video now resizes gracefully when the device is rotated to Portrait or Landscape mode.  All is better in the world.

Side note: While playing around with this, I had a javascript alert(newHeight); line in the resizing code.  on my iPad with the shiny new iOS 4.3.1 installed just last night, this promptly caused the application to crash.  I've submitted a bug report to Apple, and we'll see how it goes.

As always, comments and suggestions are always welcome.

Embedded Youtube Videos with Placeholder Images

One of the requests I've had for a website I'm currently working on is to provide a placeholder image for an embedded Youtube Video.  Essentially, the client wants to display the image until someone clicks on it, then replace it with the video.  So here's how I did it with the new uTube package for Umbraco, but the same technique can be used for any web page...

With this method, I'm not trying to play the video in a separate window, or resize anything, I'm just wanting to have the video take the place of the image on the website.  This makes things a little easier, as I can render both the video and the image in separate div's and use CSS to lay one on top of the other using z-index and position elements.  In addition, I can use some fairly straight forward jquery javascript to hide one element and show the other.

Rendering a Flash Video player with an Image overlay

Here's a sample of the HTML including embedded javascript:

    <div id="mainFeature">
        <div id="mediaVideo" style="visibility: hidden">
            <div class="youTubePlayer" style="height: 433px; width: 680px;">
                <div id="ytPlayerAPI-rldN0jSBbZQ">
                    <p>
                        You need Flash version 8 and JS enabled to view the video</p>
                </div>
                <script type="text/javascript">
                    //SWF Embedd
                    var flashVars = { video_id: "rldN0jSBbZQ", playerapiid: "ytPlayerAPI-rldN0jSBbZQ", allowFullScreen: "true" }
                    var params = { allowScriptAccess: "always", wmode: "transparent", allowFullScreen: "true" };
                    var atts = { id: "ytPlayerAPI-rldN0jSBbZQ" };
                    swfobject.embedSWF("http://www.youtube.com/v/rldN0jSBbZQ?fs=1&rel=0&enablejsapi=1&version=3&playerapiid=ytPlayerAPI-rldN0jSBbZQ", "ytPlayerAPI-rldN0jSBbZQ", "680", "433", "8", null, flashVars, params, atts);
                </script>
            </div>
        </div>
        <div id="mediaImage">
            <a href="/recipes/crispy-chinese-chicken-with-sichuan-salt-pepper" title="Crispy Chinese Chicken with Sichuan Salt & Pepper">
                <img src="/media/2245/crispy_chinese_chicken_01.jpg" title="Crispy Chinese Chicken with Sichuan Salt & Pepper"
                    alt="Crispy Chinese Chicken with Sichuan Salt & Pepper" /></a></div>
        <script type="text/javascript">

            $(document).ready(function () {
                $('div#mediaImage a').click(function () {
                    $('div#mediaVideo').css('visibility', 'visible');
                    $('div#mediaImage').fadeOut(1000);
                    return false;
                });
            });
      
        </script>
    </div>

And the corresponding CSS to initially lay out the elements:

#mediaVideo, #mediaImage {
  position: absolute;
  left: 0;
  top: 0;
}

#mediaImage {
  z-index: 10;
}

Notice that in this example I'm using the SWFObject library to render the flash player. For some reason I haven't fully explored yet, if you render the flash object using object tags, the flash player is rendered on top of the mediaImage instead of obeying the CSS rules and rendering the image div on top as it should.

The result? When the user clicks on the image, it fades out and is replaced by the flash object.

Displaying embedded Youtube Videos on an iPhone/iPad

The thing about iPhone and iPad is it doesn't yet support Flash.  However, if you use the object tags approach, the device is smart enough to recognise the Youtube player and use the built-in YouTube app in it's place.  This is a pretty cool feature, but doesn't appear to work if you use the SWFObject library approach above.  Only problem is, although iPhones etc support javascript, and will honour the code above, all you see is the following message:

Rendering Youtube on iPhone with SWFObject libraryRendering a Youtube embedded video with the SWFObject on iPhone...

And using the <object> tag approach:

Rendering Youtube on iPhone with object TagsRendering a Youtube embedded video with object tags on iPhone...

Clicking on the video launches the Youtube App.  The iPad plays the video in place in the website using the Youtube app, which is pretty cool, only launching a separate window if you view it in fullscreen mode.

The code for rendering the Youtube with object tags is as follows:

            <div class="youTubePlayer" style="height: 433px; width: 680px;">
                <object width="680" height="433">
                    <param name="movie" value="http://www.youtube.com/v/rldN0jSBbZQ?fs=1&rel=0" />
                    <param name="allowFullScreen" value="true" />
                    <param name="allowscriptaccess" value="always" />
                    <embed src="http://www.youtube.com/v/rldN0jSBbZQ?fs=1&rel=0" type="application/x-shockwave-flash"
                        allowscriptaccess="always" allowfullscreen="true" width="680" height="433" /></object></div>

The best of both worlds: Supporting iPhone and rendering placeholder Images...

It's a compromise, but since we can't get both apple devices and standard browsers to render the same thing, we need to use the HTTP_USER_AGENT string to conditionally render the different approaches.

Umbraco and the uTube plugin...

For those of us utilising the excellent new uTube plugin with Umbraco, the following XSLT code should do the trick - note that you'll have to render the Chrome player if you want Full screen mode, as the chromeless player doesn't support it.  See my earlier post about this: Enabling Alternate Media with the uTube Umbraco Package

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
  <!ENTITY nbsp " ">
]>
<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:msxml="urn:schemas-microsoft-com:xslt"
  xmlns:umbraco.library="urn:umbraco.library"
  xmlns:Exslt.ExsltCommon="urn:Exslt.ExsltCommon"
  xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes"
  xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath"
  xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions"
  xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings"
  xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:yt="http://gdata.youtube.com/schemas/2007"
  xmlns:media="http://search.yahoo.com/mrss/"
  xmlns:uTube.XSLT="urn:uTube.XSLT"
  exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath
  Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets atom yt media uTube.XSLT"> <xsl:output method="xml" omit-xml-declaration="yes"/> <xsl:param name="currentPage"/> <xsl:template match="/"> <!-- YouTube URL or VideoID --> <xsl:variable name="uTubeVideo" select="/macro/uTubeVideo" /> <xsl:variable name="allowFullScreen" select="/macro/allowFullScreen" /> <xsl:variable name="allowRelatedVideos" select="/macro/allowRelatedVideos" /> <xsl:variable name="jsPlayerApiId" select="/macro/jsPlayerApiId" /> <!-- Don't do anything unless we have a value in uTubeVideo --> <xsl:if test="/macro/uTubeVideo"> <xsl:variable name="uTubeVideoID" select="uTube.XSLT:GetVideoId($uTubeVideo)" /> <xsl:variable name="uTubeXML" select="uTube.XSLT:GetVideoData($uTubeVideoID, 60)"/> <xsl:variable name="uTubeRatio" select="uTube.XSLT:GetAspectRatio($uTubeVideoID)"/> <!-- Video Width --> <xsl:variable name="uTubeWidth"> <xsl:choose> <!-- If the Width is not set, then check if Height is available --> <xsl:when test="not(/macro/uTubeWidth) and /macro/uTubeHeight"> <!-- Use the Height to get the Width --> <xsl:value-of select="uTube.XSLT:GetVideoWidth(/macro/uTubeHeight, $uTubeRatio)" /> </xsl:when> <!-- If the Width is set, use it! --> <xsl:when test="/macro/uTubeWidth"> <xsl:value-of select="/macro/uTubeWidth" /> </xsl:when> <!-- Otherwise fall back on a default value --> <xsl:otherwise> <xsl:value-of select="number(480)" /> </xsl:otherwise> </xsl:choose> </xsl:variable> <!-- Video Height --> <xsl:variable name="uTubeHeight"> <xsl:choose> <!-- If the Height is not set --> <xsl:when test="not(/macro/uTubeHeight)"> <!-- Then use the Width to get the Height --> <xsl:value-of select="uTube.XSLT:GetVideoHeight($uTubeWidth, $uTubeRatio)" /> </xsl:when> <xsl:otherwise> <!-- Otherwise use the user-defined value --> <xsl:value-of select="/macro/uTubeHeight" /> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="allowFullScreenText"> <xsl:choose> <xsl:when test="$allowFullScreen = 1">true</xsl:when> <xsl:otherwise>false</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="uTubeRel"> <xsl:choose> <xsl:when test="$allowRelatedVideos = 1">&rel=1</xsl:when> <xsl:otherwise>&rel=0</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="enableJSApi"> <xsl:if test="$jsPlayerApiId != ''">&enablejsapi=1&playerapiid=<xsl:value-of select="$jsPlayerApiId" /></xsl:if> </xsl:variable> <div class="youTubePlayer" style="height:{$uTubeHeight}px; width:{$uTubeWidth}px;"> <xsl:choose> <xsl:when test="uTube.XSLT:AllowEmbed($uTubeVideoID) = true()"> <xsl:choose> <xsl:when test="contains(umbraco.library:RequestServerVariables('HTTP_USER_AGENT'), 'iPad') or contains(umbraco.library:RequestServerVariables('HTTP_USER_AGENT'), 'iPhone')"> <object width="{$uTubeWidth}" height="{$uTubeHeight}"> <param name="movie"
                   value="http://www.youtube.com/v/{$uTubeVideoID}?fs={$allowFullScreen}{$uTubeRel}{$enableJSApi}"></param> <param name="allowFullScreen" value="{$allowFullScreenText}"></param> <param name="allowscriptaccess" value="always"></param> <embed src="http://www.youtube.com/v/{$uTubeVideoID}?fs={$allowFullScreen}{$uTubeRel}{$enableJSApi}" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="{$allowFullScreenText}" width="{$uTubeWidth}" height="{$uTubeHeight}"></embed> </object> </xsl:when> <xsl:otherwise> <!-- Video is allowed to be embedded --> <div id="{concat('ytPlayerAPI-',$uTubeVideoID)}"> <p>You need Flash version 8 and JS enabled to view the video</p> </div> <!-- SWFObject call to Embed Chromless player --> <script type="text/javascript"> //SWF Embedd var flashVars = {video_id : "<xsl:value-of select="$uTubeVideoID"/>",
                                            playerapiid: "<xsl:value-of select="concat('ytPlayerAPI-',$uTubeVideoID)" />",
                                            allowFullScreen: "<xsl:value-of select="$allowFullScreenText"/>" } var params = {allowScriptAccess: "always", wmode: "transparent",
                                            allowFullScreen: "<xsl:value-of select="$allowFullScreenText"/>" }; var atts = { id: "<xsl:value-of select="concat('ytPlayerAPI-',$uTubeVideoID)" />" }; swfobject.embedSWF("http://www.youtube.com/v/<xsl:value-of
                                                    select="$uTubeVideoID" />?fs=<xsl:value-of
                                                    select="$allowFullScreen" /><xsl:value-of
                                                    select="$uTubeRel" />&enablejsapi=1&version=3&playerapiid=<xsl:value-of
                                                    select="concat('ytPlayerAPI-',$uTubeVideoID)" />","<xsl:value-of
                                                    select="concat('ytPlayerAPI-',$uTubeVideoID)" />", "<xsl:value-of
                                                    select="$uTubeWidth" />", "<xsl:value-of
                                                    select="$uTubeHeight"/>", "8", null, flashVars, params, atts); </script> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <!-- Video Not Allowed to be embedded --> <p>This video does not allow it to be embedded.</p> </xsl:otherwise> </xsl:choose> </div> </xsl:if> </xsl:template> </xsl:stylesheet>

Any questions, comments or suggestions, feel free to leave a comment...

Enjoy!

Enabling Alternate Media with the uTube Umbraco Package

The project I'm currently working on requires embedding youtube videos in the page. For that I've settled on the excellent uTube Umbraco package found over here (or visit the Umbraco package page on our.umbraco.org...).

Out of the box, the uTube package provides some excellent functionality - you can upload videos directly from the Media repository, skinnable player as well as the full youtube player, ready made macros for including content...

However, as the site doesn't yet have videos for all the articles, and there's no guarantee that the articles will do so in the future, I wanted to be able to provide an alternative image instead of the video if it was missing.

In addition, some videos were already uploaded to youtube, and uTube currently doesn't have an import datatype for the Media section, so I wanted to be able to provide another alternative to the user to specify a youtube url as well as be able to choose from the Media items.

So I wrapped up the uTube macro's in my own "wrapper" macro:

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp " "> ]>
<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:msxml="urn:schemas-microsoft-com:xslt"
  xmlns:umbraco.library="urn:umbraco.library" xmlns:Exslt.ExsltCommon="urn:Exslt.ExsltCommon" xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions" xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings" xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets" xmlns:CWS.Twitter="urn:CWS.Twitter" xmlns:Locator="urn:Locator" xmlns:tagsLib="urn:tagsLib" xmlns:BlogLibrary="urn:BlogLibrary"
  exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets CWS.Twitter Locator tagsLib BlogLibrary ">


<xsl:output method="xml" omit-xml-declaration="yes"/>

<xsl:param name="currentPage"/>
    <xsl:variable name="mainFeatureId" select="/macro/mainFeatureId"/>
    <xsl:variable name="mainFeature" select="umbraco.library:GetXmlNodeById($mainFeatureId)"/>
<xsl:template match="/">

  <xsl:choose>
    <xsl:when test="$mainFeature/uploadedVideo != ''">
      <xsl:variable name="macro">
        <![CDATA[<?UMBRACO_MACRO macroAlias="uTube.ChromelessPlayer.media" mediaID="]]><xsl:value-of select="$mainFeature/uploadedVideo"/><![CDATA[" uTubeWidth="680" uTubeHeight="433"></?UMBRACO_MACRO>]]>
      </xsl:variable>
      
      <!-- Render the chromeless player macro -->
      <xsl:value-of select="umbraco.library:RenderMacroContent($macro, $currentPage/@id)" disable-output-escaping="yes"/>

    </xsl:when>
    <xsl:otherwise>
      <xsl:choose>
        <xsl:when test="$mainFeature/video != ''">
          <xsl:variable name="macro">
            <![CDATA[<?UMBRACO_MACRO macroAlias="uTube.ChromelessPlayer" uTubeVideo="]]><xsl:value-of select="$mainFeature/video"/><![CDATA[" uTubeWidth="680" uTubeHeight="433"></?UMBRACO_MACRO>]]>
          </xsl:variable>
          
          <!-- Render the chromeless player macro -->
          <xsl:value-of select="umbraco.library:RenderMacroContent($macro, $currentPage/@id)" disable-output-escaping="yes"/>
        
        </xsl:when>
        <xsl:otherwise>
          <xsl:if test="count($mainFeature/bannerMedia) > 0">
            <a href="{umbraco.library:NiceUrl($mainFeature/@id)}" title="{$mainFeature/@nodeName}"><img src="{umbraco.library:GetMedia($mainFeature/bannerMedia, 'false')/umbracoFile}" title="{$mainFeature/@nodeName}" alt="{$mainFeature/@nodeName}" /></a>
          </xsl:if>
        </xsl:otherwise>
      </xsl:choose>

    </xsl:otherwise>
  </xsl:choose>

</xsl:template>

</xsl:stylesheet>

Taking a leaf out of the uTube.ChromelessPlayer.media macro's book (which is in itself a wrapper for the uTube.ChromelessPlayer macro), I'm testing for the existence of the uploadedVideo property, which points at the Media Item containing the uTube uploaded media.  If that doesn't exist, I then check for the video property, which is the alternative uTube Single Video Picker (the user simply pastes in a Youtube url).  If that doesn't exist, we fallback on a placeholder image instead.

End result: Graceful media fallback on the web page.

uTube.ChromelessPlayer.media