Home

VS All Access

Microsoft wanted a showcase piece to prove to the Internet that Silverlight could do what Flash could do. This was that proof.

The VS All Access landing page, with a full bleed Angel, a Model Search tout and a rotating small toutThe Highlights screen, filtered to a runway categoryThe Angel Line-Up screen, with photos of each Angel receding into perspectiveThe open casting screen for the Model SearchThe Model Search About screen, explaining the two ways to enterThe Model Search screen playing video in the custom player, with scrubber, volume, fullscreen and share controls

If I recall correctly, Netflix used Silverlight to stream movies on the web in the early days. Microsoft intended for it to be a Flash competitor, especially for video, but that obviously didn’t pan out. But that wasn’t for lack of trying. This is a story of how they tried and my small part in all that.

By 2009, Big Spaceship was well known for its polished Flash experiences. Adobe would often partner with us and movie studios to build showcase pieces. We’d get more budget, the client got more features & functionality, and Adobe got a case study. When one of Adobe’s principal Flash evangelists jumped ship to Microsoft, it only made sense that they’d reach out with a similar deal.

I don’t recall whether it was Microsoft or Victoria’s Secret who reached out first. What I remember is that a showcase was required for the famous Victoria’s Secret Fashion Show, that there wasn’t a lot of time (<= 3 months), and that the requirement was to build a showcase streaming experience in Silverlight.

There was only one problem: Nobody at the company had ever even opened Visual Studio or knew a thing about Silverlight.

As the solo engineer, my job was to figure all that out. I needed to teach the designers about the tech, too – we were used to collaborating on Flash timelines but we didn’t know what was even possible in Silverlight. Shine Draw’s website was essential here. A developer named Terence Tsang would build the same small thing twice, once in Flash and once in Silverlight, and post the source for both. Here is how you load a SWF. Here is how you load a XAP. It was like a Rosetta Stone for me. Stuff like this:

movieclip.x = 100; // actionscript
usercontrol.SetValue(Canvas.LeftProperty, 100); // c# / silverlight

Using that, I was able to go from prototypes to frameworks. I actually ported a good chunk of Big Spaceship’s ActionScript library to Silverlight, largely around loading and animation states.

com.bigspaceship
  • display
    • videoplayer
      • controls
      • overlays
  • events
  • loading
  • utils
using System;
using System.Collections.Generic;
using com.bigspaceship.events;
using com.bigspaceship.utils;

namespace com.bigspaceship.loading
{
    public class BigLoader
    {
        private static int __MAX_CONNECTIONS = 2;

        private bool _verbose = true;

        private List<BigLoadItem> _itemsToLoad;

        private int _curLoadIndex = 0;

        private int _activeLoads = 0;

        private int _numComplete = 0;

        private bool _loaderActive = false;

        private Dictionary<string, object> _contentDict;

        public event BigLoadItemEventHandler Progress;

        public event EventHandler Completed;

        public event EventHandler ItemCompleted;

        public BigLoader()
        {
            _contentDict = new Dictionary<string, object>();
            _itemsToLoad = new List<BigLoadItem>();
            disableLog();
        }

        public void destroy()
        {
            _contentDict.Clear();
            for (int i = 0; i < _itemsToLoad.Count; i++)
            {
                _itemsToLoad[i].Completed -= _onItemLoadCompleted;
                _itemsToLoad[i].Progress -= _onItemLoadCompleted;
                _itemsToLoad[i].destroy();
            }
            _itemsToLoad.Clear();
            _contentDict = null;
            _itemsToLoad = null;
        }

        public void enableLog()
        {
            _verbose = true;
        }

        public void disableLog()
        {
            _verbose = false;
        }

        public BigLoadItem add(string url)
        {
            return add(url, null, 1.0, null);
        }

        public BigLoadItem add(string url, string id)
        {
            return add(url, id, 1.0, null);
        }

        public BigLoadItem add(string url, string id, double weight)
        {
            return add(url, id, weight, null);
        }

        public BigLoadItem add(string url, string id, string extension)
        {
            return add(url, id, 1.0, extension);
        }

        public BigLoadItem add(string url, string id, double weight, string extension)
        {
            if (_loaderActive)
            {
                Out.fatal(this, "You can't add anything after the loader is started.");
                return null;
            }
            if (id == null)
            {
                id = url;
            }
            BigLoadItem bigLoadItem = new BigLoadItem(url, id, weight, extension);
            bigLoadItem.Progress += _onItemProgress;
            bigLoadItem.Completed += _onItemLoadCompleted;
            _itemsToLoad.Add(bigLoadItem);
            return bigLoadItem;
        }

        public void start()
        {
            if (_loaderActive)
            {
                Out.fatal(this, "Loader is already started");
                return;
            }
            _log("Starting load of " + _itemsToLoad.Count + " items.");
            _loaderActive = true;
            int num = Math.Min(_itemsToLoad.Count, __MAX_CONNECTIONS);
            while (_activeLoads < num)
            {
                _loadItem();
            }
        }

        public object getAsset(string id)
        {
            if (!_contentDict.ContainsKey(id))
            {
                _log("Warning: Asset not loaded yet.");
                return null;
            }
            return _contentDict[id];
        }

        private void _loadItem()
        {
            BigLoadItem bigLoadItem = _itemsToLoad[_curLoadIndex];
            bigLoadItem.startLoad();
            _log("Starting load of " + bigLoadItem.ToString());
            _activeLoads++;
            _curLoadIndex++;
        }

        private void _onItemProgress(object sender, EventArgs e)
        {
            double num = 0.0;
            int num2 = _itemsToLoad.Count;
            while (--num2 > -1)
            {
                num += _itemsToLoad[num2].getWeightedPercentage();
            }
            if (this.Progress != null)
            {
                this.Progress(this, new BigLoadEvent(num * 100.0));
            }
        }

        private void _onItemLoadCompleted(object sender, EventArgs e)
        {
            BigLoadItem bigLoadItem = sender as BigLoadItem;
            _activeLoads--;
            _contentDict[bigLoadItem.id] = bigLoadItem.content;
            _log("COMPLETE :: " + bigLoadItem.ToString());
            if (this.ItemCompleted != null)
            {
                this.ItemCompleted(sender, e);
            }
            _numComplete++;
            if (_numComplete == _itemsToLoad.Count)
            {
                _allLoadsComplete();
            }
            else if (_curLoadIndex < _itemsToLoad.Count)
            {
                _loadItem();
            }
        }

        private void _allLoadsComplete()
        {
            _loaderActive = false;
            if (this.Completed != null)
            {
                this.Completed(this, new EventArgs());
            }
        }

        private void _log(string str)
        {
            if (_verbose)
            {
                Out.info(this, str.ToString());
            }
        }
    }
}

Loading a XAP the way you’d load a SWF

The piece I remember fighting hardest was loading. In Flash you load a SWF, add it to the stage, and you’re done. Silverlight had no equivalent. A XAP is a ZIP, the class you want is inside a .dll inside it, and getting from one to the other is entirely your problem.

BigLoadItem is where that ended up. Pull the assembly out of the downloaded XAP by name, hand the stream to an AssemblyPart, and let it load:

string uriString = _xapNameSpace + ".dll";
StreamResourceInfo resourceStream = Application.GetResourceStream(new StreamResourceInfo(e.Result, null), new Uri(uriString, UriKind.Relative));
AssemblyPart val = new AssemblyPart();
Assembly assembly = val.Load(resourceStream.get_Stream());

Then Lib reaches into that assembly and instantiates the class by string, which is the part that actually gives you something to put on screen:

public static UserControl createMainPage(string xri, Assembly assembly)
{
    return createUserControl(xri + ".MainPage", assembly);
}

Four steps to do what loadMovie did in one. But once it was written it was written, and the rest of the site got to treat a XAP like a SWF.

Here’s the payoff, from Main. Every screen, the header, the footer, the background, the carousel and the video player are all separate XAPs listed in a config XML. The loader fetches them, Main switches on the id and news up the right class against the assembly that just arrived:

Assembly assembly = _loader.getAsset(_screenCurrent) as Assembly;
string value = screenInfoById.Attribute("class").Value;
UserControl uc = Lib.createMainPage(value, assembly);
XDocument xml = XDocument.Parse((_loader.getAsset(_screenCurrent + "_xml") as StringBuilder).ToString());
switch (_screenCurrent)
{
case "screen_landing":
    _screens[_screenCurrent] = new Landing(uc, _screenCurrent, xml, assembly, value);
    (_screens[_screenCurrent] as Landing).Navigate += _navigateTo;
    break;
case "screen_highlights":
    _screens[_screenCurrent] = new Highlights(uc, _screenCurrent, xml, assembly, value);
    break;
// ...
}

The stage is five stacked Canvas layers, built in a loop, so the background, header, footer, screen and preloader never argue about z-order:

_layers = new List<Canvas>(5);
for (int i = 0; i < 5; i++)
{
    Canvas val = new Canvas();
    ((Panel)_stageCanvas).get_Children().Add(val);
    _layers.Add(val);
}

And a section change is just: animate the old screen and background out, preload the new one, animate it in. Deep links go through the browser URL, so the back button worked, which in 2009 was not a given.

The Highlights carousel, with one photo large in the center slot and receding panels angled away on either side

One thing I always liked about working at Big Spaceship was the collaboration between coder and designer. This carousel was generally possible to code, but getting it exactly correct for the animation and positioning would’ve been a big pain. Instead, we leveraged storyboards, similar to how 3D game character reactions work. In code nothing really moves – no photos traveling anywhere. There are seven fixed positions on stage, and what rotates is the assignment of pictures to positions. Position 3 is the center slot, the big one. Positions 0 through 2 and 4 through 6 are the receding panels. Click the arrow and every slot animates one step over, wrapping around from 6 back to 0. The photo doesn’t slide anywhere. It gets handed off.

ui.widgets.carousel

Each slot is a CarouselTransition, bound to a UserControl named t0 through t6. Inside each of those is a resting Storyboard named for the slot it sits in:

_storyboardStart = _uc.FindName("p" + _positionStart) as Storyboard;

And a second set named for the hop, from the slot it’s leaving to the slot it’s arriving at:

_storyboardPlaying = _uc.FindName("p" + positionCurrent + "_" + _positionCurrent) as Storyboard;

So p2_3 is the move into the center. p3_4 is the move back out of it. Both directions had to exist, wrap included, so the XAML behind a slot carries a resting storyboard for each of the seven positions plus a transition for every adjacent hop each way. Twenty-one hand authored animations to make one arrow click look right. All the perspective, the scale, the easing, the way a panel tips as it slides toward center – none of that is in the code. The code just plays them.

prev() and next() decrement or increment a target index, wrap it mod 7, and let the timeline do the rest. If you click again mid-flight it doesn’t interrupt anything, it just moves the destination and keeps hopping one slot at a time until it gets there.

I don’t know why I didn’t make the center item 0. I wound up with code like this:

private void _nextOnClick(object sender, MouseButtonEventArgs e)
{
    for (int i = 0; i < _transitions.Count(); i++)
        _transitions[i].prev();
}

This is technically correct – moving the carousel forward means every slot moves backward! Looking at it now, I’m certain I confused myself for quite awhile during the build. I didn’t know how many photos any given gallery would have. To solve for that, I made sure we had cycled until we had enough photos:

int num2 = Math.Max(8, value.Count());

Then the last three get rotated to the front, so slot 3 starts holding the right picture instead of whatever happened to land there. Four photos, eight items, seven slots, and it spins forever without anyone noticing the repeat.

The videoplayer

One of the features we included was an embeddable video player so you didn’t need to be on the site to watch the stream – very fancy for 2009. In the interest of preservation, I’ve revived that embedded player using OpenSilver – it’s not a port, it’s the original code! It’s not the full site, but you can get a sense of the speed and styles we were able to pull off at the time.

The clip is Big Buck Bunny rather than anything from the show, for reasons I hope are obvious. Everything around it – the scrubber, the volume steps, the share overlay, the rollovers – is the 2009 code.

Some anecdotal results:

What I’m most proud of is what I overcame. Delivering something that many millions of people would see in a platform I’d never used and a language I’d never written is something I look back on with great fondness.