Repository: learningequality/kolibri

Issue:
videos not playing in mac app & Safari, but do in Firefox/Chrome
`0.13.0b3` on Mojave 10.14.3.

Both macApp and Safari seem to be having the same issue with playing videos, but Firefox & Chrome play just fine:

![2019-12-01_3-11-02](https://user-images.githubusercontent.com/1457929/69908512-586cf780-13eb-11ea-8b34-fd5f5457481b.jpg)


![2019-12-01_3-17-38](https://user-images.githubusercontent.com/1457929/69908505-3d01ec80-13eb-11ea-9b23-8a62ed5052cb.jpg)

[logs.zip](https://github.com/learningequality/kolibri-installer-mac/files/3907784/logs.zip)



Discussion:
@radinamatic Are these videos from the latest KA revision? I believe we fixed some video codec issues a little while back in ricecooker to ensure that we use video and audio codecs that work in all major browsers, so want to make sure these aren't old videos from before that fix.
Not 100% certain if the channel version is the most recent, will re-check and report back!
I´m pretty sure my testing channel was up-to-date with the latest from sources, but just to be 100% certain, I performed another sync on Studio, and updated it locally. When reviewing channel changes in Kolibri, nothing was reported:

![2019-12-07_23-47-13](https://user-images.githubusercontent.com/1457929/70381698-6bce1480-194f-11ea-8e1c-172c7be7361a.png)

The same error as above, however, persisted:

![2019-12-07_23-39-49](https://user-images.githubusercontent.com/1457929/70381705-8607f280-194f-11ea-8128-94808bc6f8c9.png)

![2019-12-07_23-41-05](https://user-images.githubusercontent.com/1457929/70381708-96b86880-194f-11ea-9ddb-2f062f1fbf82.png)




Since this appears to affect Safari also, transfering to main Kolibri repo.

@radinamatic it sounds like this is a regression?
Just a note that I tried this on my end by importing Radina Testing Channel with the latest Kolibri Mac app build on Mojave 10.14.5 and was unable to reproduce the problem. The videos load and play for me both in Safari and in the Mac app. Is it possible to upgrade the testing machine to 10.14.5?
Also, I checked the video file and it's using H.264 for video and AAC for audio, which are supported codecs.
> it sounds like this is a regression?

I have been running tests on Mojave only from 0.13.0b3, so cannot be sure if it's a regression. The [test results from High Sierra were described here back in October](https://github.com/learningequality/kolibri-installer-mac/issues/4#issuecomment-546187218).

@mrpau-richard could you re-test on your Mojave VM to confirm?
> @mrpau-richard could you re-test on your Mojave VM to confirm?

I did not reproduce this issue on macOS 10.14.5 @radinamatic , The video played properly on `Safari 12.1.1`
lowering priority because the results seem to be intermittent
Agreed, apparently only my Mojave VM has issue with this.
Is this still extant?
@pcenov Could you test this on the latest Mac app? Thank you!
Hi @rtibbles and @radinamatic, I had the latest Mac app (v0.14.7) already installed on my Mac mini (macOS Catalina 10.15.7) and when this morning I checked the videos all were playing correctly. Then I decided to check what will be the case with a 0.15 build of the app which I downloaded from [here](https://buildkite.com/learningequality/kolibri-macos/builds/3744) and which I was able to install on the mac mini but not able to run it as it shows the following error message:

![test](https://user-images.githubusercontent.com/79847249/129922540-c05a2f00-ce3a-423d-984f-fc709716a69b.jpg)

Then I decided to access the 0.15 build through the Safari browser by using my windows laptop as a server. Surprisingly the videos won't play in that case:

![test 2](https://user-images.githubusercontent.com/79847249/129923504-976cff8d-2093-4925-a21d-a5dc10a23ef9.jpg)

After that I reinstalled the 0.14.7 build and wanted to confirm that the videos are playing correctly however the app remains stuck at the loading screen and I'll have to further investigate how to get it going again. :)
Hi @rtibbles, had a chance to have a look at this one today with the [latest 0.15 build](https://buildkite.com/learningequality/kolibri-macos/builds/3804) and I can confirm that the issue is extant there - the videos are not playing neither in the app nor in Safari (14.1.2):
![safari](https://user-images.githubusercontent.com/79847249/130780082-82046620-6809-42dd-8395-600c6ac32b1a.jpg)

@rtibbles and @radinamatic the issue reported here in my last comment is still present in the [latest 0.15 build](https://buildkite.com/learningequality/kolibri-macos/builds/4939#000fe592-3efd-41fa-ac48-836d9320259b). The videos are not playing neither in the app nor in Safari (14.1.2). Videos are playing fine in Chrome browser though so it must be a specific setting that Safari requires in order to play them.

Resolving patch:
diff --git a/kolibri/utils/kolibri_whitenoise.py b/kolibri/utils/kolibri_whitenoise.py
--- a/kolibri/utils/kolibri_whitenoise.py
+++ b/kolibri/utils/kolibri_whitenoise.py
@@ -2,13 +2,17 @@
 import re
 import stat
 from collections import OrderedDict
+from io import BufferedIOBase
+from wsgiref.headers import Headers
 
 from django.contrib.staticfiles import finders
 from django.core.files.storage import FileSystemStorage
 from django.utils._os import safe_join
 from whitenoise import WhiteNoise
 from whitenoise.httpstatus_backport import HTTPStatus
+from whitenoise.responders import MissingFileError
 from whitenoise.responders import Response
+from whitenoise.responders import StaticFile
 from whitenoise.string_utils import decode_path_info
 
 
@@ -76,6 +80,40 @@ def find_location(self, root, path, prefix=None):
             return path
 
 
+class SlicedFile(BufferedIOBase):
+    def __init__(self, fileobj, start, end):
+        fileobj.seek(start)
+        self.fileobj = fileobj
+        self.remaining = end - start + 1
+
+    def read(self, size=-1):
+        if self.remaining <= 0:
+            return b""
+        if size >= 0:
+            size = min(size, self.remaining)
+        data = self.fileobj.read(size)
+        self.remaining -= size
+        return data
+
+
+class EndRangeStaticFile(StaticFile):
+    def get_range_response(self, range_header, base_headers, file_handle):
+        headers = []
+        for item in base_headers:
+            if item[0] == "Content-Length":
+                size = int(item[1])
+            else:
+                headers.append(item)
+        start, end = self.get_byte_range(range_header, size)
+        if start >= end:
+            return self.get_range_not_satisfiable_response(file_handle, size)
+        if file_handle is not None:
+            file_handle = SlicedFile(file_handle, start, end)
+        headers.append(("Content-Range", "bytes {}-{}/{}".format(start, end, size)))
+        headers.append(("Content-Length", str(end - start + 1)))
+        return Response(HTTPStatus.PARTIAL_CONTENT, headers, file_handle)
+
+
 class DynamicWhiteNoise(WhiteNoise):
     index_file = "index.html"
 
@@ -152,3 +190,25 @@ def candidate_paths_for_url(self, url):
         path = self.get_dynamic_path(url)
         if path:
             yield path
+
+    def get_static_file(self, path, url, stat_cache=None):
+        """
+        Vendor this function from source to substitute in our
+        own StaticFile class that can properly handle ranges.
+        """
+        # Optimization: bail early if file does not exist
+        if stat_cache is None and not os.path.exists(path):
+            raise MissingFileError(path)
+        headers = Headers([])
+        self.add_mime_headers(headers, path, url)
+        self.add_cache_headers(headers, path, url)
+        if self.allow_all_origins:
+            headers["Access-Control-Allow-Origin"] = "*"
+        if self.add_headers_function:
+            self.add_headers_function(headers, path, url)
+        return EndRangeStaticFile(
+            path,
+            headers.items(),
+            stat_cache=stat_cache,
+            encodings={"gzip": path + ".gz", "br": path + ".br"},
+        )


Test patch: