Ch’khalagu !
Spotify Web Playback SDK — feat. Electron
Another horror story brought to you by callbackinsanity
TL;DR: It does not work
Google Chrome ships with a multitude of modules that provide DRM (encrypted content) playback and other capabilities. The Chromium browser that ships with Electron is like a barebones Chrome browser that lacks these modules. That means that out of the box Electron is not able to play DRM’d content coming from content providers such as Youtube and Spotify.
In this post I will be addressing the lack of native Chrome modules to enable third-party content playback in Electron.
The (working) baseline
This is the Spotify Playback SDK Quick Start:
It works in a regular browser page sans-modifications. If you copy and paste the quick start code into an index.html
file, after generating the required Spotify token, then simply open the index.html
file in Chrome:
After loading the page and opening the Web Developer Tools, under the console section you should see the glorious message indicating a successful connection to the Spotify service:
Ready with Device ID xyz
If you open.spotify.com (or any other compatible Spotify player) you should get the “Web Playback SDK Guide…” device in the list of devices. Screenshot below:
So… the boilerplate simply works. Great. What about React? And what about React in Electron?
The not-so-working Electron baseline
Without involving any React code yet, if I paste this into my Electron application’s index.html (in the renderer process) — I get the following:
Failed to initialize player
I initially thought my plot’s McGuffin was cookies (due to the same-site
warning) or CSPs (Content Security Policies), but after hours playing with both I came to the conclusion that it was neither.
Moreover, after dumping hours into tinkering with adding custom CSPs to my Electron’s index.html, I couldn’t even get rid of the same-site
Cookie warnings. Probably because these seem to be something that really needs to be solved in the application placing the third-party cookies and not the application receiving them.
Here’s an example of me foolishly trying to intercept and hack third-party cookies being inserted into the browser by Spotify, in order to set their same-site
parameter, using Electron hooks:
Although I was able to eventually add the same-site
parameter to the third-party Cookies, it had no effect on the console warnings. Here’s a snapshot of how the third-party cookies look at the moment, without any modification. Note the missing same-site
parameters:
So if serving DRM’d content is not something related to Cookies or CSPs, what to do?
The Real McGuffin
Ahhh yes. Some light at the end of the rabbit hole. In Spotify Github issue #7, dated January 2018 and titled Web Playback SDK support with Electron, hughrawlinson makes the following comment:
…it looks like Electron doesn’t support Widevine CDM that’s required by the Web Playback SDK. You should try this guide to enable Widevine in Electron and report back whether or not you got it working
The guide that comment links to is not even available. Widevine (and Digital Rights Management) are not even mentioned in the current list of official guides over at the Electron Github repo.
Someone else at the that thread makes the recommendation to look at the NPM package electron-widevinecdm. But a quick look at that package’s Github repo quickly shoots down any hopes for a solution:
- The package is currently deprecated, and;
- It’s not supported on Windows.
What’s the point of using Electron if your application is not cross-compatible across all three major consumer platforms (OSX, Linux, and Windows)? None…
Luckily, an article over at electronjs.org titled Testing Widevine CDM gives us some much needed hope — and it is updated as of May 11, 2020:
So armed with nothing but openness, optimism, and the spirit of curiosity we head over to that Electron article and see what all this widevine hearsay is about.
.
A Warp into the world of Widevine and CDMs
An introduction from the Electron docs:
In Electron you can use the Widevine CDM library shipped with Chrome browser.
Widevine Content Decryption Modules (CDMs) are how streaming services protect content using HTML5 video to web browsers without relying on an NPAPI plugin like Flash or Silverlight. Widevine support is an alternative solution for streaming services that currently rely on Silverlight for playback of DRM-protected video content. It will allow websites to show DRM-protected video content in Firefox without the use of NPAPI plugins. The Widevine CDM runs in an open-source CDM sandbox providing better user security than NPAPI plugins.
There’s more mumbo-jumbo techwords in that passage than in a Star Trek episode. So let me break it down.
WideVine is a proprietary Digital Rights Management (DRM) technology owned by Google since 2010 and deployed in their browsers (Chrome), as well in Android and Firefox.
CDM stands for Content Decryption Module, and it is one of the facilities provided by the the widevine DRM technology.
NPAPI stands for Netscape Plug-in API (NPAPI). NPAPIs allow you to call into native binary code from JavaScript. But between 2013–2014 Google started sunsetting support for these. CDMs like the one provided by widevine replace NPAPIs.
So basically, using CDMs you are able to consume presumably copyrighted content from third-party providers such as Netflix, Hulu, Et. Al. from the comfort of your modern HTML5 viewing device without the need for Flash or Silverlight.
Spotify leverages the widevine content decryption module to serve all it’s glorious encrypted content, and so that’s why we’ll be needing needing to put widevine into Electron, since Chrome ships with it but Chromium in Electron doesn’t.
At this point in this journey I have two options available to me:
- Plan A: Try adding widevine support to stock Electron using a combination of Electron start-up flags and the pre-existing widevine binaries that come bundled with Google Chrome.
- Plan B: Replace stock Electron with Cast Labs fork, which comes bundled with widevine out of the box.
Each option has its own cons and pros.
The first option is fine for testing locally. But linking or copying binaries in production sounds cumbersome and unreliable at best and works on the assumption that the user has Google Chrome installed. Therefore it is not scalable to production.
The second option, while convenient, would rely on a benevolent third party to keep their Electron binary updated with the upstream Electron project.
A side-by-side comparison between the CastLabs downstream and the official Electron distributions yields the following results:
Both CastLabs and official Electron distributions have support for Chromium 80.x
, so at least there doesn’t seem to be wide disparity of packaged components between the two Electron distros.
With the version compatibility hurdle cleared, I feel more confident in using CastLabs solution. For context, CastLabs distribution has 65 stars, 10 forks, and 3 contributors on Github. For comparison Shaka Player, a JavaScript library that provides encrypted media playback capabilities and is run by WideVine (aka Google), has 4k stars, 731 forks, and 90 contributors. However I feel that comparing these two projects because both support encrypted media playback is like comparing apple to oranges and ultimately not fruitful.
.
Plan A: Electron Quick Start + Spotify Web Playback SDK Quick Start + Chrome WideVine binaries
Since I’m writing an article, in the name of thoroughness and masochism I’d like to try both approaches — using stock and forked Electron distributions — to play widevine content.
Here’s the stock Electron approach using the Electron Quick Start. From their instructions:
# Clone this repository
git clone https://github.com/electron/electron-quick-start
# Go into the repository
cd electron-quick-start
# Install dependencies
npm install
# Run the app
npm start
Results on Windows:
Now let’s try manually adding widevine support to stock Electron, to test that Electron’s official documentation regarding WideVine work as advertised.
Note: the following steps are being reproduced on a Windows 10 machine.
I open up my Electron Quick Start’s package.json
and replace the start
instruction from
"start": "electron ."
to
"scripts": {
"start": "run.bat"
},
Then in the same directory as the package.json
I create a batch file for Windows with the following contents:
To run the Electron Quick Start application, I type:
npm start
Here’s a screenshot of the command to start Electron in action:
I have modified my main.js to load the contents of https://shaka-player-demo.appspot.com/ instead of the stock index.html
that ships with the Quick Start by replacing this:
// and load the index.html of the app.
// mainWindow.loadFile('index.html')
With this:
mainWindow.loadURL('https://shaka-player-demo.appspot.com')
I have also added plugins: true
to the Electron’s BrowserWindow.webPreferences
option as mentioned in https://github.com/castlabs/electron-releases#using-the-widevine-cdm-in-electron-for-content-security:
const win = new BrowserWindow({
webPreferences: {
plugins: true
}
});
To recap what I’ve done so far:
- Downloaded Electron Quick Start demo application.
- Modified the demo’s
package.json
start command to run a Windows batch file namedrun.bat
. - Added the widevine flags indicated by the Testing Widevine CDM article to the
run.bat
script. - Modified the demo’s
main.js
to load the URL shaka-player-demo.appspot.com instead ofindex.html
, as recommended by the testing article mentioned above. - Ran the modified Electron Quick Start application using
npm start
, in a Windowscmd.exe
shell.
The Result
.
The initial result of the modified demo shows that I’m not able to play the widevine-encrypted content:
Passing the --widevine-cdm-path
and--widevine-cdm-version
parameters via the command line do not work.
To verify whether widevine is even loaded, opening the Web Developer Tools and typing inspector.plugins
into the console should not yield an empty result, which it does in my case (meaning, bad news):
By comparison, on stock Kosher Google Chrome you get an all-you-can-get buffet of DRM facilities:
The React tutorial working (not surprisingly) on Google Chrome, as demonstrated by the populated navigator.plugins
array:
.
Time For An Horror Story
Here on Medium, people like to post curated stories with all kinds of happy endings. But we all know that the real world is not all happy stories, especially here on the world of development. Call it Success Porn.
If you’re like me, and like keep it real and not sugarcoat reality head over to Electron Github issue #12427 (March 2018) for a real horror story.
Our main protagonist here (yoannmoinet, the issue reporter) never reported to find a resolution to the issue of using widevine in production Electron. And our co-star ccj242 only reported a happy resolution on March 25, 2020 … two years afterwards!
This is more Jordan Peele then Ch’khalagu (dissapointing) terror territory here.
For ancillary entertainment read issues #359, #671, and The Reg’s reporting about this saga — No Widevine DRM for you! Developer left with two years of work stymied by Google snub.
Conclusion?
Electron’s documentation over at Testing Widevine CDN is more wide-eyed (success porn) and optimistic (incomplete) than it lets on, even after getting an update as part of #12427. It didn’t work for me as sold, and judging by the Github issues listed above, it probably won’t — since there is more than meets the eye at getting Widevine support working, and therefore, things like Hulu, Netflix, and Spotify in your Electrons.
For example, it doesn't even mention that you need to have plugins: enabled
when you instantiate an ElectronWebBrowser
instance, as directed over here.
To clarify, widevine’s lack of developer accessibility is less of Electron’s fault (if any at all, really), and more of a capitalist monopoly and regulation meta-issue.
Thankfully, it’s not all sour-grapes as if I haven’t done this simple demo, I could have spent countless more hours (or days) trying to get widevine to work on stock electron without success. The only success stories over at issue #12427 used the CastLabs Electron fork, which may or may not work right now — since the powers that be keep trying to make DRM accessibility as in-accessible as possible by the day.
That brings us to Plan B in this story, because no plan survives first contact with the enemy.
.
Time For Plan B
For Plan B I’m going to be using the Electron fork provided by CastLabs over at https://github.com/castlabs/electron-releases/, and following the template set at issue #12427.
With this I should be able to (in theory) successfully fire up an Electron application with baked-in widevine DRM support, and therefore load up Spotify, Netflix, or any other DRM’d content provider — at least for development purposes.
As for production, as I documented in today’s story well … that’s another story. One impossible thing at a time.
After pursuing the goal of using Spotify’s Web Playback API in Electron for a week and writing this article over what felt like a full working day, I’ll be writing a second story with journey into Plan B (widevine with CastLabs Electron fork). I’ll be linking back to this article — if I don’t give up!
.
Update 5/15/2020
- Note: When sharing binaries from Chrome to Electron, the version of Chrome on both applications need to match for the widevine binaries to work. However, YMMV according to this issue — https://discuss.atom.io/t/playing-netflix-in-electron-widevine-cdm-cant-get-it-to-work/37827
.
.
Bibliography
Some ideas:
- https://mbell.me/blog/2017-12-29-react-spotify-playback-api/
- https://stackoverflow.com/questions/54469738/adding-a-js-function-to-reactjs-spotify-web-playback-sdk
- https://medium.com/@bilawalhameed/getting-started-with-async-await-with-the-spotify-web-playback-sdk-aac4b6bf7105
About Content Security Policies CSPs
About CSPs…
- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- https://content-security-policy.com/
- https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html
- https://www.electronjs.org/docs/tutorial/security#6-define-a-content-security-policy
About same-site cookies
- https://www.chromium.org/administrators/policy-list-3/cookie-legacy-samesite-policies
- https://blog.chromium.org/2020/04/temporarily-rolling-back-samesite.html
- https://textslashplain.com/2019/09/30/same-site-cookies-by-default/
- https://blog.chromium.org/2020/04/temporarily-rolling-back-samesite.html
- https://cloud.google.com/docs/chrome-enterprise/policies/?policy=LegacySameSiteCookieBehaviorEnabled
- https://cloud.google.com/docs/chrome-enterprise/policies/?policy=LegacySameSiteCookieBehaviorEnabledForDomainList
- https://www.chromium.org/updates/same-site
Spotify’s API
- https://stackoverflow.com/questions/48760545/how-to-know-when-the-user-changes-the-song-with-the-spotify-api
- https://developer.spotify.com/documentation/
- https://developer.spotify.com/documentation/web-playback-sdk/quick-start/#
- https://developer.spotify.com/documentation/web-api/reference/player/
- https://developer.spotify.com/documentation/web-api/guides/using-connect-web-api/
- https://developer.spotify.com/documentation/web-api/reference/player/
- https://developer.spotify.com/documentation/web-playback-sdk/reference/#api-spotify-player-pause