We have a well-used app that streams certain videos. They are streamed as partial content. The backend for this system is in Clojure, using Reitit with the Ring approach. However, while they work great in Firefox and Chrome, videos fail to load in Safari. Originally we thought it was the lack of a content type (since they are referenced from UUIDs without a file extension), but fixing this did not solve the problem. Then we thought perhaps it was a webkit problem, since webkit is the engine under Safari and Safari itself is impossible to install off of mac (unless you choose an archaic version from 2017, which probably wouldn’t represent anyone anyway). So I tried Epiphany, purportedly one of the best Webkit options on Linux. Alas, it didn’t show the problem, either! So, I am left with a problem that is apparently only Safari, not even webkit, that they cannot run our videos – disabling the large number of students who are trying to use their iphones, ipads, or macbooks to view the course material videos. Has anyone run into or solved this problem? Do you know why other webkit browsers (and indeed every other vendor) have no problem, but all Mac devices on Safari-type browsers are failing?
This is 100% a wrong place to ask. You’d have a much higher chance of getting a proper answer on any Apple-specific or even a generic web dev forum.
Originally we thought it was the lack of a content type (since they are referenced from UUIDs without a file extension)
Just wanted to address this - file extensions have nothing to do with content type. In browsers, content-type is a separate header that you should set to a proper value in the response.
You should also check whether the video format is supported by Safari in the first place. Plenty of compatibility tables online for that.
Another thing worth mentioning is that on iOS both Chrome and Firefox also use WebKit.
The range header shouldn’t affect the playback itself, it should only affect the ability to seek through the video. But Safari has shown itself as an eccentric browser more than once, so who knows.
The backend is Clojure and we are using Ring to mediate the content, with a front end using raw react (not reagent), hence our grasping at straws for the possibility it was the range header. I asked here hoping someone has navigated this issue. By default, ring will assume the content type based on the file extension, so we had suspected that problem first. Thanks for the general information about the other browsers using webkit (or is it Apple’s mutation of webkit?) on Mac as well. We had wondered if range that had been the problem as we tried to compare working services (ie YouTube) to see what differences were there; that was, at present, our last idea. Maybe the next steps are to get myself a mac so we can try it’s Safari out to see if we can isolate the actual error and test possible fixes, and to test raw mp4 without streaming as partial media to see if that, at least, works (in which case Ring might be to blame).
How you build a response is irrelevant. Ring + Clojure, SimpleHTTPServer + Python, nc, whatever - doesn’t matter.
Focus on the content of responses, not on how they’re produced. Even if Ring doesn’t provide some header or something else that Safari needs, it’s not Ring itself that’s to blame, it’s how you use it to create responses.
You have given very little detail so it’s hard to provide any actual insight. But ensuring a tight feedback loop by getting your hands on macOS probably makes the most sense. Then you’ll be able to effectively and efficiently compare working resources with non-working. And you don’t need to own a physical Apple hardware for that.
Thanks for that. I had just hoped that for such a large user-base as “iOS users” a basic technical hurdle like “streaming video” might be addressed with minimal fuss
You can also try to host the videos on S3 or Google Cloud Storage. I don’t know if it will help with your specific Safari issue. But at least S3 and Google Cloud Storage handles all the range queries correctly.
However, you might consider to use something like:
It will prepare your videos automatically so that you can use adaptive streaming (DASH and HLS) which is important for mobile users with varying connection speeds. They offer a video player implementation that handles all the details for you.
Regarding range-requests, I got them working in the sunng87/jetty9-wrapper - which is using Jetty 12. I have no idea if this would solve any Safari-related problems.
and a configuration function is needed for setting up a Jetty ResourceHandler thing (all the terms here are confusing - a Resource is a webresource, not a JVM-resource, a Handler is, of course, not a ring handler but a Jetty handler class).
(defn configurator
"returning a configurator that can add a resource handler"
[{:keys [resources-path
welcome-files
directories-listed?
accept-ranges?
uri-path] :as opts
:or {accept-ranges? true
directories-listed? false
welcome-files ["index.html"]
uri-path "/assets"}}]
(fn
[s]
(log/info "puts context in a contexthandlercollection")
(let [resource-handler (doto (new ResourceHandler)
(.setBaseResource (org.eclipse.jetty.util.resource.Resource/newResource resources-path))
(.setDirectoriesListed directories-listed?)
(.setWelcomeFiles (into-array welcome-files))
(.setAcceptRanges accept-ranges?))
file-path-context (new ContextHandler uri-path)
_ (.setHandler file-path-context resource-handler)
jetty-adapter-context-handler (.getHandler s)
ch-coll (new ContextHandlerCollection (into-array [file-path-context jetty-adapter-context-handler]))]
(.setHandler s ch-coll))))
This code excerpt is happening before the ring-adapter → ring-handler, so any authentication using ring machinery has to be solved in some other way. It would be really nice to be able to return back to the ResourceHandler from inside Ring, but I’m not sure how that could be made to work.
I messed around with ffmpeg once and that worked ok for a very basic test. Never took it anywhere though. Didn’t really want to deal with video encoding stuff, so scrapped the entire thing.
It definitely isn’t anything reitit, ring or Clojure would even begin to do. So, ffmpeg is where you should probably start looking.
I think you’d be (significantly) better off by using an off-the-shelf video streaming server setup (for HLS or DASH) than re-implementing it in your Clojure application. You could have your application point to the relevant URLs served by that second server.
Sorry I wasn’t clear! If you want HLS, there’s a bunch of servers that can do that (see here). If you want to do DASH, then there are many options for preparing the content (encoding, MPD, segments, etc). You can then serve those from any web server, and there are JS clients that can deal with DASH content.