iOS / Apple Rules That Must Never Be Broken
These are enforced at the OS level — break them, and you get native fullscreen + broken custom UI.
Which is actually what is happening on mobile.
CALL | What it does for us vs. apple |
---|---|
playsinline | Prevents forced fullscreen in iOS Safari |
webkit-playsinline | Legacy support for older Safari versions |
muted | Allows autoplay on mobile |
controls | Must be included for initial rendering; Video.js will override |
Use setControls(false) | Disables iOS native control overlay after load |
Avoid autoplay without muted | Safari won’t allow it inline |
Don’t rely on disabling fullscreen via JS | iOS ignores it; only inline playback prevents it |
What we have to do
- Get a static ID / data-attribute on the player (or by studio) so that we can targget it easily – and get it set up to work after the plugins have run
- Use the correct player options to disable native iOS behaviors (do what they want or they take over with THEIR player)
- Add playsinline, muted, and control bar – necessary attributes to the “video” tag (for IOS)
- Clean up any injected tracks and ensure plugin timing works
- Work changes into the templlate – so it lasts.
Setting static ID / data-attribures (call from head)
<script>
document.addEventListener("DOMContentLoaded", function () {
setTimeout(() => {
Object.values(videojs.players).forEach(player => {
player.ready(function () {
const el = this.el();
// Add a static class - maybe per studio?
el.classList.add('js-video-ready');
// Add a stable data attribute for future targeting
el.setAttribute('data-player-id', 'main-video');
// Optional: Tag the <video> element itself too
const tech = el.querySelector('video.vjs-tech');
if (tech) {
tech.setAttribute('data-player-id', 'main-video');
}
// Later you can use:
// document.querySelectorAll('[data-player-id="main-video"]')
});
});
}, 300); //wait for everyting to initialize
});
</script>
In the video player itself – we need to make apple happy so it doesnt’ decide to TAKE OVER the player.
- Use muted, playsinline, and webkit-playsinline
- Call setControls(false) via player.tech() after .ready()
- you can’t stop it with = player.exitFullscreen() — it only works after native fullscreen triggers
If you skip any one of these, Safari often falls back to native fullscreen.
Player code in backend
<video
id = "video_<?=$set['Id']?>"
class="video-js vjs-fluid"
playsinline
webkit-playsinline <!--to keep ios from getting it fullscreen and tossing away videojs-->
muted <!--to follow apples rules-->
preload="auto"
crossorigin = "anonymous"
controls
poster = "<?=$poster_img?>"
data-setup="{}"
>
<source src="<?=$trailer_file?>" type="application/x-mpegURL">
</video>
Should make this on front end:
which does not “break” any of apples rules – and triggers the IOS native player in safari:
<video id="video_<?=$set['Id']?>" class="video-js vjs-fluid" muted playsinline webkit-playsinline controls preload="auto" poster="<?=$poster_img?>" >
<source src="<?=$trailer_file?>" type="application/x-mpegURL">
</video> <videoid="video_<?=$set['Id']?>" class="video-js vjs-fluid" muted playsinline webkit-playsinline controls preload="auto" poster="<?=$poster_img?>" >
<source src="<?=$trailer_file?>" type="application/x-mpegURL">
</video>
Cleanup the text-track and more no IOS settings
this repalces what we have for the player creation:var player = videojs('video_=$set['Id']?>', {});
(where you call the player up)
<script>
function initVideoPlayer(videoId) {
const player = videojs(videoId, {
html5: {
nativeTextTracks: false,
nativeControlsForTouch: false
},
controlBar: {
fullscreenToggle: true
}
});
if (player.nuevo) {
player.nuevo({
shareMenu: false,
settingsButton: true,
rateMenu: false,
zoomMenu: false,
qualityMenu: true
});
}
player.ready(function () {
player.tech().setControls(false);
player.one('loadedmetadata', function () {
Array.from(player.textTracks()).forEach(track => {
const el = track.el || track._el;
if (el && el.parentNode) {
el.remove();
} else {
track.mode = 'disabled';
}
});
});
player.el().classList.add('js-video-ready');
});
return player;
}
document.addEventListener("DOMContentLoaded", function () {
initVideoPlayer('video_<?=$set['Id']?>');
});
</script>
Multiple players on same page?
document.querySelectorAll('video.video-js').forEach(el => {
initVideoPlayer(el.id);
});
AND THE CSS – you already did this I think –
video::-webkit-media-text-track-display {
display: none !important;
}