I've been wanting a feature in YouTube for months now, and finally got to the point where I just decided to make it myself. The problem: if I'm playing a video on my laptop, how do I continue playing that video on my phone?

Currently, the best way I could find was to right-click the YouTube video and select "Copy video URL at current time". I'd then use AirDrop to send that over to my phone. However, it'd tend to open in Safari rather than the actual YouTube app. Not good!

As any UX designer knows, the best design for a user is often something that takes as few clicks as possible. But I also didn't want to require any installation on the phone side – I have a few mobile devices and don't want to maintain anything for them.

The final product works like this:

  • On any YouTube video, you can click a button next to the URL bar in Chrome
  • A QR code pops up
  • Scanning that QR code leads straight to the same YouTube video, at the same time

This relies somewhat on two iOS features:

  • The Camera app scans for QR codes by default; and
  • YouTube links are opened in the app

I believe the second is also true for Android phones – but they'd have to use a dedicated QR code scanner app, which is another tap away. The iOS camera, by contrast, is available even on the lock screen.

Here's what the popup looks like:

It's not big enough to block out the video (and disappears as soon as you click off it). But it's easily big enough to immediately be recognised by the camera on my iPhone.

To define the behaviour and permissions of our Chrome extension, we use a Manifest file, manifest.json.

{
    "name": "Youtube Resume",
    "permissions": ["declarativeContent", "activeTab"],
    "version": "0.0.1",
    "description": "Resume YouTube videos on another device!",
    "background": {
	"scripts": [ "background.js" ],
	"peristent": false
    },
    "page_action": {
	"default_popup": "popup.html",
	"default_icon": {
	    "128": "icon.png"
	}
    },
    "icons": {
	"128": "icon.png"
    },
    "manifest_version": 2
}

To make a popup appear when we click the button, we configure background.js to use a PageAction: this lets us declaratively specify that the popup should only appear while on YouTube.

// background.js

chrome.runtime.onInstalled.addListener(function() {
  chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
    chrome.declarativeContent.onPageChanged.addRules([{
      conditions: [new chrome.declarativeContent.PageStateMatcher({
        pageUrl: {hostEquals: 'www.youtube.com'},
      })
      ],
          actions: [new chrome.declarativeContent.ShowPageAction()]
    }]);
  });
});

The popup itself is just an HTML page, and it's one of the simplest I've ever written – it really doesn't have to do much.

<!DOCTYPE html>
<html>
  <body>
    <div id="qrcode"></div>
    <script src="qrcode.js"></script>
    <script src="popup.js"></script>
  </body>
</html>

So, how do we replace our empty "qrcode" div with a real QR code? Easy – we use a library. I grabbed a copy of [qrcode.js](https://davidshimjs.github.io/qrcodejs/), because I've done enough Canvas drawing recently and I don't mind taking a shortcut.

To get the URL, we have to do some slightly convoluted things. The JavaScript which displays the QR code is running in the context of the extension, but – although I think there is a way to get the URL of the current page – there's no way of asking for the current time of the YouTube video. For that, we need access to the DOM, which the extension doesn't have.

To get around this, we inject code into the active page. (Don't worry – if this was distributed, we'd have to ask the user's permission to do this when they install the extension!). This code grabs the HTML5 video element from the page and requests the current time. It also grabs the URL of the current page, and strips out any outdated time information using a regular expression.

// Inject a script into the active page.
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
    chrome.tabs.executeScript(
        tabs[0].id,
	{ file: "inject.js" }
    );
});

To allow this script to be run multiple times, we use an IIFE – an immediately-invoked function expression – to prevent any errors caused by redeclaring const values like "url".

(function(){
    const time = Math.floor(
      document.querySelector(".html5-main-video").currentTime
    );
    const href = window.location.href.replace(/\&t\=\d+/g, '');
    const url = `${href}&t=${time}`;
	
    // Send a message back to the extension:
    chrome.runtime.sendMessage({url: url});
})();

Our injected script, though, can't affect the QR code displayed in the extension. To do that, we need to send a message back to the extension's JavaScript code – which we've configured to listen out for these messages and update the QR code accordingly.

let url = "";
const qr = document.getElementById("qrcode");
const qrCode = new QRCode(qr, url);

// Listen for new messages.
chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
	const url = request.url;
	qrCode.makeCode(url);
    }
);

So, to summarise:

  • The extension injects a script into the YouTube page when clicked
  • This script calculates the current time, formats the URL and sends it back to the extension
  • The extension can then display a QR code linking to this URL.

Here's a screenshot of my iPhone scanning this QR code and popping up a quick action. Just tapping this notification takes me straight into the YouTube app and starts playing the video from the exact second I left off on my desktop. Neat!