So I have a small website that shows different streams from web cameras around the city. (ex: https://camstream.alteco.lv:8443/live/camera1.m3u8). The stream can be viewed here: https://camstream.alteco.lv/camera/76d4b7f895df
This is a simple page with just the video player trying to play the stream using hls.js where it can be used and native <video>
tag for iOS devices.
All was working well until iOS 15 rolled out which broke the streams, so they are not viewable anymore. It simply shows that it’s loading but nothing happens. Interestingly enough, that if I try to open the video in full screen and try seeking, the video instantly shows up. I also checked the incoming .m3u8 and .ts files using Inspect Element and everything seems to be fine.
At first, I thought that the issue is with the stream itself, but that doesn’t seem to be the case, as I’ve tried all kinds of different options and profiles (baseline, main, high) but nothing changes, so it almost feels like a bug with the safari video player, but other streams from different sites I’ve found work fine. I’ve also tested the stream using the VLC app on iPhone and it works fine there. It only doesn’t work on Safari.
I’m using nginx with the nginx-rtmp module to server the stream to clients.
daemon off;
error_log /dev/stdout info;
events {
worker_connections 1024;
}
rtmp {
server {
listen 1935;
chunk_size 4000;
application stream {
live on;
exec ffmpeg -i rtmp://localhost:1935/stream/$name
-c:a aac -c:v libx264 -vf yadif -b:v 1100k -f flv -g 60 -r 30 -s 1280x720 -force_key_frames expr:gte(t,n_forced*2) -preset superfast -profile:v main rtmp://localhost:1935/hls/$name_720p2628kbs
-c:a aac -c:v libx264 -vf yadif -b:v 600k -f flv -g 60 -r 30 -s 854x480 -force_key_frames expr:gte(t,n_forced*2) -preset superfast -profile:v main rtmp://localhost:1935/hls/$name_480p1128kbs
-c:a aac -c:v libx264 -vf yadif -b:v 300k -f flv -g 60 -r 30 -s 640x360 -force_key_frames expr:gte(t,n_forced*2) -preset superfast -profile:v main rtmp://localhost:1935/hls/$name_360p878kbs;
}
application hls {
live on;
hls on;
hls_fragment_naming system;
hls_fragment 6s;
hls_path /opt/data/hls;
hls_nested on;
hls_variant _720p2628kbs BANDWIDTH=2628000,RESOLUTION=1280x720;
hls_variant _480p1128kbs BANDWIDTH=1128000,RESOLUTION=854x480;
hls_variant _360p878kbs BANDWIDTH=878000,RESOLUTION=640x360;
#hls_variant _240p528kbs BANDWIDTH=528000,RESOLUTION=426x240;
#hls_variant _240p264kbs BANDWIDTH=264000,RESOLUTION=426x240;
}
}
}
http {
server {
listen 80;
listen 443 ssl;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /opt/data;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
}
location /live {
alias /opt/data/hls;
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
}
location = /crossdomain.xml {
root /www/static;
default_type text/xml;
expires 24h;
}
}
}
The code for the player page is also dead simple
<video id="video" autoplay muted controls playsinline preload="metadata"></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script src="//code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script>
$(document).ready(function () {
var video = document.getElementById('video');
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource('{{$camera->m3u8}}');
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
video.play();
});
hls.on(Hls.Events.MEDIA_ATTACHED, function () {
//can we get cam on load?
setTimeout(function () {
fitToScreen()
}, 500);
})
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
console.log("No HLS support. Switching to native <video>")
video.src = '{{$camera->m3u8}}';
video.addEventListener('loadedmetadata', function () {
console.log("loadedmetadata");
video.play();
});
}
$(window).resize(function () {
fitToScreen()
}).resize();
setInterval(function () {
fitToScreen()
}, 3000);
})
</script>
I’ve also tried switching to video.js but that didn’t help. The same issue happened.
I’ve been trying to resolve this issue for the last 4 days without any success, so I’m hoping that someone has encountered something like this which could point me in a direction where to look at
4
Answers
In the end I couldn't solve the problem with nginx-rtmp module and I simply switched to Node-Media-Server https://github.com/illuspas/Node-Media-Server
With this, the streams work great on all iOS versions
I had a similar problem, and this is how I solved it:
I had a nginx-rtmp container with image alfg/nginx-rtmp, based in alpine linux. I tried solve the problem using others nginx-rtmp docker container, all this images are based in alpine linux.
I solved the problem by creating new image based in ubuntu linux and in the nginx.conf configuration I changed the command push for exec ffmpeg.
If I not change push command for exec ffmpeg, the problem is not solved.
My scenario is:
CAMS RTMP/RTSP –> SERVER 1 (only for receive live image of cams and proces with ffmpeg) –> SERVER 2 (receibe live image from SERVER1 and streaming live for users)
I needed change to ubuntu in SERVER1 and SERVER2
This problem seems to be present with combination of iOS 15 or higher + nginx_rtmp_module.
We solved this with ffmpeg. Added "exec ffmpeg …." function to nginx.conf (nginx_rtmp_module). so the HLS stream is created by ffmpeg.
If anyone comes across this issue and is (just like me) having issues with the NGINX RTMP module and the Node Media Server (on iOS this one gives me a stuttering sound), the solution for me was to use the open source version of MistServer.