Video Playback Rate Hackery

Photo by Noom Peerapong on Unsplash

Some video sites, like YouTube, provide a way to change the playback speed of the video. This allows you to watch content faster or slower than the standard speed. It is an incredibly useful feature for many people and until earlier this year, I thought it was ubiquitous. I am so used to using it that when I wanted to watch a video and it was not available, I got quite frustrated1.

Thankfully, a colleague showed me that all is not lost and I want to share the magic with you.

Above is the video I wanted to watch (source). It is an interesting video packed with useful information about testing, but it is quite long. I really wanted to watch it, but I did not have a lot of time available, so decided to watch it at a faster speed. However, I could not find a way to adapt the playback speed within the site's user experience. What to do?

In swoops my colleague, via Slack, to remind me that I have browser developer tools at my finger tips, primed to let me make it happen anyway. You can make this happen on the Vimeo site, the TestDouble site, or even here, on my blog.

In the world of HTML5, videos are embedded in pages using the video element. The video element implements the HTMLMediaElement interface (as does the audio element) and if you can get a reference to the video element in JavaScript, you can use this interface to manipulate the video playback.

The first step is getting the element. I did this in Google Chrome, but you should be able to do this in other browsers too, though the commands may be different. I right-clicked on the video and selected Inspect.

Screen grab of the right-click menu, showing the commands "View Page Source", "View Frame Source", "Reload Frame" and "Inspect"

This should open the developer tools, with a node highlighted.

A snippet of HTML as shown in the Chrome Dev Tools. Some items are collapsed, hiding the child HTML. One node is highlighted.

As seen in the screenshot above, the highlighted node was not the video element. Initially, I looked at the sibling elements and expanded likely candidates until I found the video tag I wanted, but there is an easier way. Use the Find command within the Elements tab of the Chrome developer tools by pressing ⌘+F (probably Ctrl+F on Windows).

A screengrab of the find bar in the Elements tab of the Google Chrome developer tools. The text "<video" has been entered and "1 match" is indicated as found.

Within the find bar, you can type <video and it will find the first video element in the page and, if there are more, allow you to cycle through any others until you get to the one you want. You can even tell if it is the one you want or not as both the node and the corresponding video are highlighted as seen in the screenshot below.

Screenshot showing the highlighted node found in the DOM via the Chrome developer tools on the right and the corresponding video highlighted in the page itself on the left.

With the video element found, we can right click the DOM node in the developer tools and select Store as global variable.

Screen grab showing the found "video" element and the right-click menu with the "Store as global variable" option highlighted.

This creates a global variable that we can use to manipulate the element. The console section is opened to show us the created variable and the element to which it refers.

The console of the Chrome developer tools showing the name of the global variable we created "temp1", and the video element it refers to.

Now we can use the variable (temp1 in this case) to adjust the playback rate (or anything else we wanted to do). For example, if we want to run at double speed, just change playbackRate to 2 by entering temp1.playbackRate = 2.

Screenshot of the text "temp1.playbackRate = 2" having been entered in the developer tools console and the result "2" being returned to confirm that value is set.

And that's it. Hit play on that video and it will now be running at twice normal speed. Want it to run at half speed instead? Set playbackRate to 0.5 instead. Want it to run at normal speed again? Just set playbackRate to 1.

I hope y'all find this as helpful as I have and next time you stumble because a common feature appears to be lacking, don't be afraid to crack open those developer tools and see what magical hackery you can perform.

  1. "quite" is British for "inconsolably" []

Manning Up

I struggle every day. I know you do too. We may have similar struggles, or different ones. We may notice each others struggles, or we may not. What you may struggle with, I may find easy or not even realise is a thing. The same is true in reverse. Sometimes, I am struggling so hard, I forget you are too. I am really sorry about that. I do not mean to disregard you or your struggles, or give the impression that these things do not matter. That is one of my struggles.

Sometimes, when my heart is heavy with the anxiety of a personal revelation, a growth opportunity, I find it hard to see beyond to a point where it will all make sense again. So, I write things like this. I am not seeking pity or likes or anything like that. I only want to share how I am feeling in case you struggle with this thing too. I know it helps me to know I am not alone and sharing helps that, because if we never share our pain, how can we appreciate each other's joy?

I struggle between putting out into the world that somedays I feel broken, and "manning up", dealing with it privately like "men" do. Some folks reading this will not be able to look past the ideals they have of what it is to be strong and will see this as weak; that's one of their struggles.

And so, to close. Whatever you struggle with, it is okay to not be okay. Take some time. Sit in the sun with a cup of tea, or pet a cat, or whatever it is that helps you escape the moment and find a little pocket of peace. Maybe even share a story about it and help someone else who is finding that same struggle. You are stronger than you think and we are all stronger together than apart. You are loved. You are you, and no one else can be a better version of that than you already are. <3

Keyboard Shortcut Shortcuts

This is a short post and hopefully incredibly helpful to some. There is (almost) a standard around finding out basic information about keyboard shortcuts in your favourite apps, and it came to my attention this week that not everyone knows about it. What I am about to tell you may change the way you use some of your most cherished applications forever, or it may not.

Almost every web application supports a keyboard shortcut for seeing more keyboard shortcuts using either ⇧ + / or ⌘ + /1. Using these you can quickly get to a summary of keyboard shortcuts for your app of choice. Even some locally installable desktop apps have taken to this "standard" shortcut for showing shortcuts. Go now and try it in something.

For those who aren't into experimenting with shortcuts, here are a few apps and their shortcuts shortcut. Search the table for your app of choice. If you happen to know about any other apps that use this approach should be in this list, let me know in the comments.

Happy keyboarding!

ApplicationShortcut
Asana⌘ + /
Bitbucket⇧ + /
Confluence⇧ + /
Desmos⌘ + /
Facebook⇧ + /
Figma⇧ + ⌥ + /
Github⇧ + /
Gmail⇧ + /
Google Calendar⇧ + /
Google Docs⌘ + /
Google Drive⇧ + /
Google Hangouts⇧ + /
Google Meet⌘ + / or ⇧ + /
Google Sheets⌘ + /
Google Sites⌘ + /
Google Slides⌘ + /
Jira⇧ + /
Onedrive⇧ + /
Slack⌘ + /
Trello⇧ + /
Tweetdeck⇧ + /


Featured photo derived from photo by Andrew Worley on Unsplash

  1. By ⌘, I mean either the looped square key on an Apple computer, or the Windows key on a PC []

New Job, New Tech

Hello, world!

You might have noticed I took a little break from my blog recently. It was not intentional; things just got away from me a bit the last few months as I found a new job and had a nice vacation to see family in England (as well as a side trip to Edinburgh and the famous Fringe festival). Perhaps I will post more on the vacation another time; right now, I want to share my job news.

After a fantastic four years with CareEvolution, Inc., I recently accepted a software engineering position with Khan Academy. I am only a few weeks into my new position and I am still incredibly excited to have this opportunity. Not only am I working with some incredible people, we have tasked ourselves with an outstanding mission.

Our mission is to provide a free, world‑class education for anyone, anywhere.1

Leaving CareEvolution, Inc. was a difficult decision. Not only did it mean leaving behind extraordinary colleagues, it also meant leaving behind PowerShell, C#, Angular, and .NET as a part of my day-to-day profession. Instead, I will be working with React, Redux, Apollo, and Python. There is much for me to learn and, I hope, for me to blog about as I learn it. That said, I still love .NET things and will continue to tinker with them in my personal time2.

Of course, like my passion for .NET, some things will remain the same. Most significantly for me, the position is still remote and as such, provides me with great opportunities for personal growth as an offsite colleague and employee. I openly3 struggled with that while at CareEvolution, Inc. I hope that at Khan Academy, I can learn which parts of that struggle were down to the need for personal growth, and which, if any, were organisational. If I can, I will coalesce lessons I learn into a meaningful collection of tips that others might use to adapt their personal and organisational culture around remote work and off-site workers.

Finally, this blog is still my blog, these are my personal musings; nothing I post here represents the views of my employer. Thank you for your readership and your patience during my blog hiatus. As they say at work, onward!


Featured Image by Todd Quackenbush on Unsplash

  1. https://www.khanacademy.org/about []
  2. I have already started developing .NET core on OSX []
  3. perhaps too openly []

Using PowerShell to make BrowserStack local testing easier with Microsoft Edge

BrowserStack has been an incredibly useful resource for tracking down bugs and testing fixes when I am working on websites. This often requires accessing locally deployed sites or sites accessible over a VPN connection, and to do that, BrowserStack needs some local code running to be able to route the traffic accordingly.

BrowserStack logoUp until recently, my browser of choice has been Google Chrome, for which BrowserStack provides a handy extension to add support for local sites. However, since the Windows Creators Update, I have been giving Microsoft Edge a shot1 and no such extension exists. Instead, BrowserStack provides a download, BrowserStackLocal.exe , and a secret with which to run it. This works great, but there are a couple of annoyances.

  1. I have to remember to run it.
  2. It is a blocking process.

There are a variety of ways this problem can be solved. I decided to take the opportunity to expand my PowerShell fu and put together some cmdlets to run the BrowserStackLocal process in the background. Specifically, I wanted to compare PowerShell jobs with plain old processes for this specific purpose.

First: Jobs

Since the running the command is a blocking operation, I decided to try wrapping it in a PowerShell job so that it would sit in the background. This is useful since the job gets terminated when the PowerShell session ends, which makes it less likely for me to forget. The downside is that each PowerShell session could have its own job, but only the one that started BrowserStackLocal will actually end it, but I was certain I could work with that.

Getting started

The first cmdlet for starting BrowserStackLocal is cunningly named Start-BrowserStackLocal , shown here:

function Start-BrowserStackLocal()
{
    $browserStackSecret = "<YOUR-SECRET-HERE>"
    $browserStackLocalDir = "F:\Program Files (x86)\BrowserStackLocal"
    $bslocal = Get-Command "$browserStackLocalDir\BrowserStackLocal.exe" -ErrorAction SilentlyContinue

    $job = Get-Job BrowserStackLocal -ErrorAction SilentlyContinue
    if ($job) {
        Write-Host "BrowserStackLocal already started: " -ForegroundColor Yellow -NoNewline
        Write-Host "$($job.ID):$($job.Name)" -ForegroundColor Cyan
        return
    }
    
    if (-Not $bslocal) { throw "Cannot find BrowserStackLocal" }

    Write-Host "Starting BrowserStackLocal..." -ForegroundColor Yellow
    $job = Start-Job -Name BrowserStackLocal -ScriptBlock {
        try {
            Push-Location $using:browserStackLocalDir
            & $using:bslocal.Path $using:browserStackSecret
        }
        finally {
            Pop-Location
        }
    }

    if2
    {
        Write-Host (Receive-Job $job)
        Remove-Job $job
    } else {
        Write-Host "Started " -NoNewline -ForegroundColor Yellow
        Write-Host "$($job.ID):$($job.Name)" -ForegroundColor Cyan
    }
}

This has basic room for improvement, like having the secret and the path be parameters to the cmdlet, or environment variables; I happened to stop tinkering once it worked for me, so feel free to expand on it.

At the start, we check to see if we already have a job for BrowserStackLocal since we only need one. If we do not, then we get on with making sure BrowserStackLocal can be found where we expect it. If everything looks good, then the job gets started.

To tackle the chance that my script may fail due to the BrowserStackLocal command either getting an incorrect key or discovering it is already running, I added a Wait-Job call. The nice thing here is that since normally BrowserStackLocal blocks, we can assume that if the job did not reach a completion state, then the executable command is running. I take advantage of that fact, so if the Wait-Job returns, we can assume things went wrong and dump the details of the problem back to the console.

Stopping the job

Once the job is running, we need to be able to terminate it.

function Stop-BrowserStackLocal()
{
    $job = Get-Job BrowserStackLocal -ErrorAction SilentlyContinue
    if (-Not $job) {
        return
    }
    Write-Host "Stopping BrowserStackLocal: " -ForegroundColor Yellow -NoNewline
    Write-Host "$($job.ID):$($job.Name)" -ForegroundColor Cyan -NoNewline
    Write-Host "..." -ForegroundColor Yellow
    Stop-Job $job
    Remove-Job $job
}

This is a much simpler cmdlet than the one to start the job. It has two main tasks:

  1. See if the job is actually running
  2. If it is, stop it

I added some helpful output so we could see it working and that was that.

Output from jobs-based cmdlets
Output from jobs-based cmdlets

Problems with jobs

This solution using jobs works great but it is not ideal. Each PowerShell session has its own jobs, so you have to know which session actually started BrowserStackLocal in order to stop it. Not only that, but if PowerShell did not start it at all, you cannot stop it from there with these commands at all. Jobs are great but they are not really the right tool for this…er…job.

Second: Processes

The wise man would have probably started here. I did not because I wanted to learn about jobs. Now that I have, I am wiser and so, I thought I would recreate my success but this time using the Xxx-Process  cmdlets of PowerShell.

Getting started again

Using processes, the start cmdlet looks like this:

function Start-BrowserStackLocal()
{
    $browserStackSecret = "<YOUR-SECRET-HERE>"
    $browserStackLocalDir = "F:\Program Files (x86)\BrowserStackLocal"

    $processes = Get-Process BrowserStackLocal -ErrorAction SilentlyContinue
    if ($processes) {
        Write-Host "BrowserStackLocal already started: " -ForegroundColor Yellow
        foreach ($process in $processes) {
            Write-Host "    $($process.ID): $($process.Name)" -ForegroundColor Cyan
        }
        return
    }

    $bslocal = Get-Command "$browserStackLocalDir\BrowserStackLocal.exe" -ErrorAction SilentlyContinue
    if (-Not $bslocal) { throw "Cannot find BrowserStackLocal" }

    Write-Host "Starting BrowserStackLocal..." -ForegroundColor Yellow
    Start-Process -FilePath $bslocal.Path -WorkingDirectory $browserStackLocalDir -ArgumentList @($browserStackSecret) -WindowStyle Hidden

    Wait-Process -Name BrowserStackLocal -Timeout 3 -ErrorAction SilentlyContinue
    $processes = Get-Process BrowserStackLocal -ErrorAction SilentlyContinue
    if ($processes)
    {
        foreach ($process in $processes) {
            Write-Host "    $($process.ID): $($process.Name)" -ForegroundColor Cyan
        }
    }
}

Since the BrowserStackLocal executable starts more than one process, I added some loops to output information about those processes. Now if we try to start the command and it is already running, we will get the same feedback, regardless of where the command was started.

Stopping the process

Switching to processes makes the stop code a little more complicated, but only because I wanted to provide some additional detail (we could have just called Stop-Process BrowserStackLocal and it would stop all matching processes).

function Stop-BrowserStackLocal()
{
    $processes = Get-Process BrowserStackLocal -ErrorAction SilentlyContinue
    if (-Not $processes) {
        Write-Host "BrowserStackLocal is not running"
        return
    }
    Write-Host "Stopping BrowserStackLocal..." -ForegroundColor Yellow
    foreach ($process in $processes) {
        Write-Host "    $($process.ID): $($process.Name)" -ForegroundColor Cyan
        Stop-Process $process
    }
}

Output from process-based cmdlets
Output from process-based cmdlets

Helpful aliases

Finally, to make the task of starting and stopping a little less arduous, I added some aliases (inspired by the helpful sasv and spsv aliases of Start-Service and Stop-Service).

Set-Alias sabs Start-BrowserStackLocal
Set-Alias spbs Stop-BrowserStackLocal

Conclusion

TL;DR: Use processes to start processes in the background3.

The rest

I am pretty pleased with how this little PowerShell project worked out. I get to keep using Microsoft Edge with minimal effort beyond what I had when using Google Chrome for my BrowserStack testing, enabling me to take advantage of the performance and battery-life improvements Edge has over Chrome. Not only that, but I got to learn some new things about PowerShell.

  1. You don't get closures entirely for free in PowerShell. I suspected this, but I learned the hard way. However…
  2. We can pass local variables into script blocks using the $using:<variable> syntax instead of passing an argument list and adding parameters to our script.
  3. Debugging jobs can be a pain until you learn the value of Receive-Job for getting error information.
  4. Use Wait-Job with a little time out to give your job chance to fail so that you can spit out some error information.
  5. You have to stop a job before you can remove it.
  6. Don't use jobs to control background processes; use processes instead

I have not gone so far yet as to start the BrowserStackLocal service automatically, but I can see value in doing so, especially if I did a lot of BrowserStack testing on local sites every day (of course, I'd probably want to redirect the output to $null in that scenario rather than see feedback on the running processes with every shell I opened).

What are your thoughts? Do you use PowerShell jobs? Do you use BrowserStack? Will you make use of these cmdlets? Fire off in the comments.

  1. Yes, the battery life is noticeably better than when using Chrome; yes, I am frustrated that I cannot clear cookies for a specific site []
  2. Wait-Job $job -Timeout 3 []
  3. Well, duh. But if I had taken that attitude, I would not have learned about jobs. []