SharePoint Media Center Additional Play Functionality
Guest Authors: Joan Resnick Ehrlich and Gerard J. Pinzone
Joan: We use WSS 3.0 for our Intranet and our setup is pretty much out of the box server-side. Gerard wanted help in setting up a way to host training related FLV video files on SharePoint and provide a SharePoint list which utilizes sorting, filtering, and grouping on metadata. I had previously set up a “media center” list to host access to WMV (Windows Media Player) files stored on one of our file servers, and I thought the same solution would also work for Gerard’s needs.
My list is based on Mark’s “SharePoint Multimedia Center” setup, which uses Lytebox and Christophe Humbert’s Text-to -HTML script (5 Minute Screencast: Create a Multimedia Center in SharePoint), and the related solution that Kevin Hughes worked out for his scenario (Creating a user-friendly media library using Lytebox), which uses a script to dynamically load the chosen WMV file in an embedded Windows Media Player (WMP). The page used to embed WMP is an aspx page without the Master Page attached, created using SharePoint Designer. (More on that later.)
After uploading the FLV files to the file server and duplicating my list setup, I ran into several issues trying to play the files. First, Windows Server 2003 IIS 6.0 requires a MIME type be added for FLV files to be recognized as streamed media, as documented here: Windows 2003 Server does not stream FLV videos. As I am the IT admin and have control over IIS that was an easy fix to implement.
Second, although the latest versions of Windows Media Player will play FLV files, the FLV file type is primarily associated with Flash, not WMP. Launching an FLV file generated the “Windows cannot open this file…” error. I did not want to change any file type associations, so my fix was to use an FLV player. I downloaded the free JW Player 5 from Longtail Video and uploaded the player files to the site resources library. To test the player, I created a webpage, embedded the FLV player into a Content Editor Web Part (CEWP), and used a hard coded path to one of the FLV files. (Longtail Video has a wizard to help generate the necessary embed code.) The file played successfully.
However, the FLV files did not play when launched from the list. The Lytebox opened, but the FLV player did not launch. I realized I needed different scripting, to handle JW Player instead of WMP. Here’s where Gerard stepped in. He wrote the necessary script, and took the solution several steps further.
Gerard: The script had to handle three of the most popular video formats in use on the web: FLV, WMV, and MOV. All three formats require different methods of playback. The script also had to provide the desired video height and width to the player. In addition, I decided to handle some issues between Lytebox and Internet Explorer (IE), which I provide at the end of this article. The goal was to create a system that would be transparent to the user and work seamlessly for all formats.
To handle the different formats: JW Player 5 uses JavaScript to dynamically create a container with all the appropriate settings and inserts the result in a <DIV> block. I used this method as a template for all of the video containers. This provides consistency across all formats and lowers the odds that an upgrade to JW Player will require adjustments to the code.
To handle height and width: I added two columns, named height and width, to the SharePoint list to contain the desired values for each video. I handled blank values by providing defaults in the script if no values were passed. The various media players require an additional amount of height for the player’s controls. Windows Media Player needs 46 pixels, QuickTime needs 16 pixels, and JW Player needs 24 pixels. I have automated this by determining the player needed for each file extension and increasing the height accordingly. If additional file formats or changes to the size of the controls occur, the offset values will need to be adjusted in the list’s calculated column formula (provided later) and in the script.
Lastly, we needed a way of parsing multiple variables from a URL. (The URL in our case is obtained from the list’s calculated column.) A Google search uncovered the JavaScript getValue function from this site: http://www.tek-tips.com/faqs.cfm?fid=5442.
I chose to place the script in an html page (named videoplayer.html) rather than an aspx page. Writing the page from scratch allowed me to have complete control over how the embedded video would be displayed. The goal was to get the video, and just the video, tightly inside the Lytebox iframe. The final code including the html code for the page is shown below. (To create the html page, copy and paste the code into a text editor and save the file with a .html extension. Then upload the html file to your chosen SharePoint library.)
Lines 17 and 90 of the script will change based on the location of the swfobject.js and player.swf files on your SharePoint site.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <style type="text/css"> /*<![CDATA[*/ <!-- body { margin: 0 auto; padding: 0px; } embed, object { margin: 0px; padding: 0px; } --> /*]]>*/ </style> <script type='text/javascript' src='/sites/yoursite/yourlibrary/jwplayer/swfobject.js'> </script> <script type='text/javascript'> //<![CDATA[ function getValue(varname) { // First, we load the URL into a variable var url = window.location.href; // Next, split the url by the ? var qparts = url.split("?"); // Check that there is a querystring, return "" if not if (qparts.length == 0) { return ""; } // Then find the querystring, everything after the ? var query = qparts[1]; // Split the query string into variables (separates by &s) var vars = query.split("&"); // Initialize the value with "" as default var value = ""; // Iterate through vars, checking each one for varname for (i = 0; i < vars.length; i++) { // Split the variable by =, which splits name and value var parts = vars[i].split("="); // Check if the correct variable if (parts[0] == varname) { // Load value into variable value = parts[1]; // End the loop break; } } // Convert escape code value = unescape(value); // Convert "+"s to " "s value.replace(/\+/g, " "); // Return the value return value; } //]]> </script> </head> <body> <div id='mediaspace'> This text will be replaced </div> <script type='text/javascript'> //<![CDATA[ var vidplayer_filename = getValue('name'); var vidplayer_width = parseInt(getValue('width')); var vidplayer_height = parseInt(getValue('height')); var vidplayer_filetype = vidplayer_filename.substr(vidplayer_filename.lastIndexOf('.')).toLowerCase(); if (vidplayer_filetype == '.flv') { var vidplayer_preview = getValue('preview'); if (!vidplayer_width) vidplayer_width = 320; if (!vidplayer_height) vidplayer_height = 240; // add 24 pixels to height for Controls vidplayer_height += 24; var so = new SWFObject('/sites/yoursite/yourlibrary/jwplayer/player.swf', 'mpl', vidplayer_width, vidplayer_height, '9'); so.addParam('allowfullscreen', 'true'); so.addParam('allowscriptaccess', 'always'); so.addParam('wmode', 'opaque'); so.addVariable('file', vidplayer_filename); so.addVariable('image', vidplayer_preview); so.addVariable('autostart', 'true'); so.write('mediaspace'); } else if (vidplayer_filetype == '.mov') { if (!vidplayer_width) vidplayer_width = 320; if (!vidplayer_height) vidplayer_height = 240; // add 16 pixels to height for Controls vidplayer_height += 16; var mov = '<OBJECT classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" width="' + vidplayer_width + '" height="' + vidplayer_height + '" codebase="http://www.apple.com/qtactivex/qtplugin.cab">'; mov += '<param name="src" value="' + vidplayer_filename + '">'; mov += '<param name="autoplay" value="true">'; mov += '<param name="controller" value="true">'; mov += '<param name="loop" value="false">'; mov += '<param name="scale" value="aspect">'; mov += '<EMBED src="' + vidplayer_filename + '" width="' + vidplayer_width + '" height="' + vidplayer_height + '" scale="aspect" autoplay="true" controller="true" loop="false" pluginspage="http://www.apple.com/quicktime/download/"><\/EMBED>'; mov += '<\/OBJECT>'; document.getElementById('mediaspace').innerHTML = mov; } else { // Override lytebox.js end function to stop video playback when closed if (parent.myLytebox) { parent.myLytebox.end = function(caller) { var closeClick = (caller == 'slideshow' ? false : true); if (this.isSlideshow && this.isPaused && !closeClick) { return; } this.disableKeyboardNav(); this.doc.getElementById('lbMain').style.display = 'none'; this.fade('lbOverlay', (this.doAnimations ? this.maxOpacity : 0)); this.toggleSelects('visible'); if (this.hideFlash) { this.toggleFlash('visible'); } if (this.isSlideshow) { for (var i = 0; i < this.slideshowIDCount; i++) { window.clearTimeout(this.slideshowIDArray[i]); } } if (this.isLyteframe) { // Inserted by G. Pinzone to stop playback of WMV files if Lytebox iframe is closed try { var videoPlayer = this.doc.getElementById('lbIframe').contentWindow.document.getElementById('MediaPlayer'); if (videoPlayer) { videoPlayer.stop(); //videoPlayer.removeNode(true); } } catch(e) {} // End this.initialize(); } } } if (!vidplayer_width) vidplayer_width = 320; if (!vidplayer_height) vidplayer_height = 240; // add 46 pixels to height for ShowControls, 26 pixels for ShowStausBar, and 74 for ShowDisplay vidplayer_height += 46; var wmv = '<OBJECT ID="MediaPlayer" WIDTH="' + vidplayer_width + '" HEIGHT="' + vidplayer_height + '" CLASSID="CLSID:22D6F312-B0F6-11D0-94AB-0080C74C7E95" STANDBY="Loading Windows Media Player components..." TYPE="application/x-oleobject">'; wmv += '<PARAM NAME="FileName" VALUE="' + vidplayer_filename + '">'; wmv += '<PARAM name="autostart" VALUE="true">'; wmv += '<PARAM name="ShowControls" VALUE="true">'; wmv += '<PARAM name="ShowStatusBar" VALUE="false">'; wmv += '<PARAM name="ShowDisplay" VALUE="false">'; wmv += '<EMBED TYPE="application/x-mplayer2" SRC="' + vidplayer_filename + '" NAME="MediaPlayer" WIDTH="' + vidplayer_width + '" HEIGHT="' + vidplayer_height + '" ShowControls="1" ShowStatusBar="0" ShowDisplay="0" autostart="1"> <\/EMBED>'; wmv += '<\/OBJECT>'; document.getElementById('mediaspace').innerHTML = wmv; } //]]> </script> </body> </html>
The basic format of the URL used in this script is:
http:// sharepoint-path-to-file/videoplayer.html?name=url-to-video.flv&height=XXX&width=YYY
Creating the player links in the SharePoint list’s calculated column
Expanding on the list’s calculated column, the concatenate command below constructs the necessary html to include the height and width in the URL that is passed to the script, and display a “Download” icon next to the “Play” icon. The “Play” icon will trigger the Lytebox iframe, while the download simply links to the source file automatically.
=CONCATENATE("<DIV><a href='/sites/yoursite/yourlibrary/yourfile.html?name=",FileName,"&width=",width,"&height=",height,"' rel='lyteframe' title='Stop video before closing window' rev='width:",width,"px",";","height:", IF(LOWER(RIGHT(FileName,4))=".flv",height+24,IF(LOWER(RIGHT(FileName,4))=".mov",height+16,height+46)),"px",";scrolling: no;'>","<img src='/sites/yoursite/yourlibrary/yourplayVideo.gif' style='vertical-align: middle' border='0' width='32' height='32' title='Click to stream video' alt='Play Icon' />","</a><span style='vertical-align: middle;padding: 0 6px 0 6px'>/</span><a href=",FileName,"><img src='/sites/yoursite/yourlibrary/yourdownload.gif' style='vertical-align: middle' border='0' width='32' height='32' title='Click to download video' alt='D/L Icon' /></a></DIV>")
The items “yoursite”, “yourlibrary”, and “yourfile/playVideo/download” will need to be modified based on the structure of your SharePoint site and the icons used.
Lytebox and Internet Explorer
Patching Lytebox.js
Displaying the video “on top of” the existing SharePoint page is accomplished by using Lytebox 3.22. Lytebox hasn’t been officially updated since October 2007. IE browsers using Lytebox do not provide the correct width or height to the page being displayed in a “lyteframe.” Therefore, I patched the changeContent code in the lytebox.js file:
if (this.isLyteframe) { var iframe = myLytebox.doc.getElementById('lbIframe'); var styles = this.frameArray[this.activeFrame][2]; var aStyles = styles.split(';'); for (var i = 0; i < aStyles.length; i++) { if (aStyles[i].indexOf('width:') >= 0) { var w = aStyles[i].replace('width:', ''); iframe.width = parseInt(w.trim()) + (this.ie ? 6 : 0); } else if (aStyles[i].indexOf('height:') >= 0) { var h = aStyles[i].replace('height:', ''); iframe.height = parseInt(h.trim()) + (this.ie ? 4 : 0); } else if (aStyles[i].indexOf('scrolling:') >= 0) { var s = aStyles[i].replace('scrolling:', ''); iframe.scrolling = s.trim();
This is considered a bug fix. The changes are applicable for all other uses of lytebox on your site.
Also, the animations work very slowly in IE browsers, so it’s best to turn that feature off (i.e., this.doAnimations = false;).
Stopping WMP audio after the iframe is closed
Internet Explorer does not shut down videos embedded with Windows Media Player (WMP) in an iframe when the iframe is closed. The iframe goes away, but the audio keeps going until the end of the video is reached. To fix this problem, lytebox needs to stop the video whenever the iframe is closed. I accomplished this by overriding the lytebox “end” function in my script whenever WMP is used in the videoplayer.html page. If a new lytebox.js ever gets released with a modified “end” function, this portion may need to be updated.
Guest Author: Joan Resnick Ehrlich
Joan Resnick Ehrlich has been in the IT industry for 15 years and is Corporate IT Administrator for a mid-sized company on Long Island, NY. Prior to entering the industry Joan was a business researcher, and she enjoys combining her research skills with IT work. In addition to SharePoint, her primary responsibilities include Windows Server, Active Directory, Exchange Server, and SQL Server.
Guest Author: Gerard J. Pinzone
Gerard J. Pinzone works in the Product Assurance department of the same firm. He has over 20 years of software development work experience, specializing in Process/Product Management, Configuration Management, Quality Assurance, Computer Technology, and Engineering including ISO 9001 and CMMI certification. Gerard had previously worked for the New York City Transit Authority as a Project Manager/Senior Staff Scientist for all aspects of the MetroCard fare system. http://www.linkedin.com/pub/gerard-pinzone/16/844/b23
Joan,
Nice article collaboration. Question to you specifically though. You mention storing wmv files on your file server. How are you accessing those files from SharePoint? Did you setup a webdav http access to the file share on the file server and utilize that url address within your SharePoint lists?
Thank you,
Rick Black
Rick,
Thanks. The specific steps: I set up a local file share (but a remote file share would also work) to which users have Read access. I set up a virtual directory in IIS under the SharePoint – 80 website and pointed it to the file share, with Read and Directory Browsing checked. WSS3/MOSS exclude unmanaged paths so I did not have an issue with SharePoint in that regard. I should note that as we only need to give access to domain users, Integrated Windows Authentication is the only authentication enabled. Anonymous access is disabled.
Joan
Joan,
Thank you. I ran some test scenarios in my VM and got that virtual directory to work as a child path of the SharePoint parent path. I’m debating whether to simply store the screencasts we are making within SharePoint (simplifies everything) or utilize the virtual directory. Obviously I don’t want to store gigabytes of screencasts within SQL, but I’m finding most of my short screencasts are running around 8 to 10mb. I’ll test both scenarios and see how it goes. Thanks!
Rick
I’d like to add that the method used for media playback through lytebox in this article can be applied to non-SharePoint websites, too. It’s pretty much a drop in solution. The only additional work that would be needed would be to port the CONCATENATE formula to asp, php, etc. or to simply hard-code the html statically on the page.
After the article was submitted, I discovered Opera and WebKit browsers display a vertical scrollbar on the videos. This can be fixed by adding the line:
html { overflow: hidden; }
to the videoplayer.html file’s CSS section. Also, the CONCATENATE formula should use escaped ampersands. I’ve noticed a lot of websites generate the same kind of non-standard, but apparently perfectly functioning, URLs. If your concerned about W3C compliance, or if you’re just a perfectionist like me, do a search and replace on that formula before plugging it into SharePoint.
I am unable to get the concatenate formula to work. I get an “refers to a column that does not exist” error message. Besides the URLs for the path to the HTML, the “play” and “download” images, does anything else need to be change specific to my environment? Does “FileName” need to change too?
Make sure your column name match what’s in the formula. If not, then change the formula to match what you have. Do you have width and height columns?
Thanks. I am going to re-create my list from scratch to be sure. What about “FileName”? Is that a variable or is that meant to be replaced with the “title” of the video — as in the orignal version of the formula? Sorry – I am new to all of this….but learning quickly.
Also, I’ve uploaded the html page to my document library. Is there anything else I need to do with it? I didn’t know if I need to include it on the site page or on a separate web part page some how.
FileName is a column that contains the URL to the video file. The html file is referenced in the formula: “/sites/yoursite/yourlibrary/yourfile.html”
Just edit the formula to match the location and name of the html file.