Prevent Sleep With the Screen Wake Lock API
It’s Tuesday night. You’re winding down with some YouTube videos on your phone, when suddenly, your screen goes dark. Agh! You tap your phone to revive, nearly spilling your drink in the process.
No dice.
You better enter your password again if you want to resume the best of Dr. Steve Brule.
Sleepy screens
Oh wait. This wouldn’t happen…and not because you’re too good for YouTube on a Tuesday night, but because your phone’s browser is smart enough to stay awake while videos are in play. Videos aside, what can we do when we’re building web applications that foster minimal user interaction and we need the user’s device to stay awake? One solution is to use David Walsh’s wakelock shim. For the intents and purposes of this blog post, however, I’d like to give a taste of what the future may hold: the Screen Wake Lock API.
I came upon this gem when I set out to build an exercise timer for myself. It’s the perfect solution because the last thing I need when I’m crushing gains doing physical therapy back stretches is my screen timing out and messing with my rhythm. Straight from MDN, “The Screen Wake Lock API provides a way to prevent devices from dimming or locking the screen when an application needs to keep running." Here are some other pertinent notes:
- As of this writing, the Screen Wake Lock API is experimental with intentions of becoming a W3C Recommendation.
- It currently works with Chrome, Edge, Opera, WebView Android, Chrome Android, and Opera Android.
- Https is required.
- Page visibility is required (e.g. navigating to a different tab “releases” the lock).
Let’s get to work!
We’re not going to build the whole timer in this post; we’re simply focusing on the WakeLock
With a timer application, we only care about preventing the device from sleeping when the timer is active. Let’s create a new variable called wakeLock
and set it to null
. In addition, because this spec is experimental, let’s open up a try/catch
block so unsupported browsers can fail gracefully and not mess up our gains whole program:
let wakeLock = null
try {
// WakeLock acquisition will go here
} catch (err) {
console.log(`${err.name}, ${err.message}`)
}
In supported web browsers, we acquire a WakeLock by calling the Promise-based navigator.wakeLock.request()
method, which is available on the global window
object. Once the promise resolves, we gain access to a WakeLockSentinel
object and our device is set to stay awake. Let’s wrap our try/catch
block in an async
function so we can await
access to the WakeLockSentinel
. Our code should now look like this:
let wakeLock = null
const acquireLock = async () => {
try {
wakeLock = await navigator.wakeLock.request('screen')
} catch (err) {
console.log(`${err.name}, ${err.message}`)
}
}
Perhaps things will change in the future, but for now, the wakeLock’s request
method only takes one argument: the type
of request. There is also only one type
, "screen"
, which gets passed in as a string. If we call our acquireLock()
function and console.log
our wakeLock
variable, we’ll see its released
state is set to false
:
WakeLockSentinel {onrelease: null, released: false, type: "screen"}
onrelease: null
released: false
type: "screen"
__proto__: WakeLockSentinel
Perfect! This is exactly what we want. In a falsy released state, our screen will stay awake…as long as we don’t navigate away (e.g. by changing browser tabs) or perform an action that would “release” the lock, such as calling the WakeLock’s release
method (more on that soon).
Sidenote: is it just me, or do you also think of sentinels in the X-Men cartoon series when you hear WakeLockSentinel
?
Impeccable hiding spot if I say so myself, Sentinel.
Once our exercise timer begins, we’ll call our acquireLock
function. When our time runs out, we need to release the lock…well…we don’t need to…we could drain our user’s battery instead!
But let’s be nice. Remember, our WakeLock’s released
state is currently false
. To reverse it and allow our devices to sleep again, the WakeLockSentinel
object has a release
method. It returns a promise that when resolved, sets the released state to true
. Sometimes I like shaking things up with my promises, so I’m going to use the .then()
syntax here, but feel free to use async/await
again:
const releaseLock = () => {
if (wakeLock) {
wakeLock.release().then(() => {
wakeLock = null
})
}
}
So we’ve accounted for starting and stopping our timer. But what if Dwayne “The Rock” Johnson calls while we’re in the middle of one of his workouts? CLEARLY we’re taking that call…and that means we just navigated away from our exercise timer. Oh no! I don’t know what’s worse: the fact we released our WakeLock or realizing it was Dwayne “The Pebble” Johnson on the phone - history’s worst celebrity imposter.
Visibility State
Meanwhile, back in reality, our exercise timer is still running but our screen won’t perpetually stay awake any longer. We need a way to reacquire our WakeLock when a user leaves and then navigates back to our application. We can tell whether or not a user is active on our page via the Page Visibility API. Let’s crack open our console and type, document.visibilityState
. It should output the string, "visible"
.
To see what happens when the user drops visibility to this tab, break out your console into its own window. If you’re in Chrome, you can do this by clicking the three vertical dots in the top-right section of the panel, and then selecting the double rectangle icon. Each browser is a bit different, but they should behave relatively similar.
Open up a new browser tab and navigate back to the console that’s logging information for the other page. If we type document.visibilityState
now, it should return the string, "hidden"
. Great! This is just what we need. Let’s first create a function to handle visibilityState changes:
const handleVisibilityChange = () => (
document.visibilityState === 'hidden' ? releaseLock() : acquireLock()
)
When this function is called, we check to see if the page is visible. If not, we’ll release our lock. If it is, we’ll reacquire it. Now we just need to call this function when a user leaves and/or bounces back to our page. Time to wire up our event listener:
document.addEventListener('visibilitychange', handleVisibilityChange)
NICE. Now we can crush gains, talk to the Rock, AND not worry about our device timing out at inopportune times and locking our screen.
For further reading, check out the spec, follow the GitHub repo to stay up to date on this feature, or read Harry Potter or something…I didn’t specify what you should further read. You do you and thanks for stopping by!