Merge lp:~ivle-dev/ivle/mediahandlers into lp:ivle
- mediahandlers
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 1830 |
Proposed branch: | lp:~ivle-dev/ivle/mediahandlers |
Merge into: | lp:ivle |
Diff against target: |
444 lines (+202/-60) 6 files modified
ivle/dispatch/request.py (+5/-0) ivle/webapp/filesystem/browser/media/browser.css (+1/-0) ivle/webapp/filesystem/browser/media/browser.js (+163/-33) ivle/webapp/filesystem/browser/media/editor.js (+25/-24) ivle/webapp/filesystem/serve/__init__.py (+1/-0) services/serveservice (+7/-3) |
To merge this branch: | bzr merge lp:~ivle-dev/ivle/mediahandlers |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Matt Giuca | Approve | ||
David Coles | Approve | ||
Review via email: mp+31040@code.launchpad.net |
Commit message
Description of the change
A series of HTML5 based media handlers using the <audio> and <video> tags. This replaces the previous page that just showed a download link (which is already available on the menu).
Known issues are bug #588285. External BHO will not be able to play media due to not having IVLE cookie.
David Coles (dcoles) wrote : | # |
> It looks fantastic! But I see some problems.
>
> * The type handlers. Firstly, looking at the type_handlers dictionary in
> ivle/webapp/
> "text" and "video" handlers. I'm a bit confused as to how audio and image
> handlers work (but I have seen them work, so ... please explain).
type_handers is just an override mechanism to allow us to map special MIME types (mainly application/x) to a specific hander. By default it uses the first part of the content type. From the code:
* If a file is not on the list, its default action is determined by the first
* part of its content type, where "text/*", "image/*" and "audio/*" are
* treated as above, and other types are simply treated as binary.
> * Secondly, I'm not quite sure how the file extension maps onto the handler,
> but it seems to be a multi-stage process. First, the Python side maps the
> filename onto a MIME type, right? This would be something like "audio/ogg" or
> "video/ogg". Then our JavaScript maps the MIME type onto a handler type, like
> "video" or "audio". The problem is that if you have a ".ogg" file, Python will
> (I think) map it onto "audio/ogg", so our player will present an OGG Theora
> video with a ".ogg" extension as an audio file. I suppose there's nothing we
> can do about this except tell people to use ".ogv" instead. (So this is a non-
> issue, unless you can think of a better solution.)
Yes. We just mimetypes.
I'll fix the override for "application/ogg". This should be mapped to "audio".
> * I'm seeing my test video with a length of 1 second, even though it continues
> playing for minutes. My test video is
> http://
> something wrong with the video. Can you offer any suggestions as to why this
> is? (I see that you added a content-length output to the download link, so
> that should be working fine. Could be related to the following problem.)
See https:/
In short, unless we set the X-Content-Duration header or allow Partial Content requests the browser can't work out how long the file is until the end since duration is not in the ogg headers.
> All of the above are permissible. I am rejecting this merge due to the
> following:
>
> * The major problem: I'm seeing in Firebug that the video is being downloaded
> in full *twice*. I know that Mozilla Firefox downloads the video multiple
> times to do buffering, and Mozilla has told us that this doesn't actually
> equate to double the bandwidth as they cache it. But this is not that -- the
> browser actually downloads the full file from two separate URLs.
Right. This is bug #588337.
> Now this was a problem before as well, because we would download the contents
> 1 time if it was binary when we should download 0. But it's much worse with
> video because we download the c...
Matt Giuca (mgiuca) wrote : | # |
> type_handers is just an override mechanism to allow us to map special MIME
> types (mainly application/x) to a specific hander. By default it uses the
> first part of the content type.
Oh OK. That makes sense.
> Yes. We just mimetypes.
> versions of Python return "application/ogg", newer return "audio/ogg". It's
> now recommended that one should treat all ".ogg" files as audio. See
> http://
>
> I'll fix the override for "application/ogg". This should be mapped to "audio".
OK. I'm happy with this behaviour (after that fix is applied).
> In short, unless we set the X-Content-Duration header or allow Partial Content
> requests the browser can't work out how long the file is until the end since
> duration is not in the ogg headers.
Oh yes. I remember this issue now. We should probably set X-Content-Duration. But that can be done after the merge (and after 1.0.2). Is there a bug on this? If not I'll file one now.
> > * The major problem: I'm seeing in Firebug that the video is being
> downloaded
> > in full *twice*. I know that Mozilla Firefox downloads the video multiple
> > times to do buffering, and Mozilla has told us that this doesn't actually
> > equate to double the bandwidth as they cache it. But this is not that -- the
> > browser actually downloads the full file from two separate URLs.
>
> Right. This is bug #588337.
OK. I didn't notice that bug before. I've posted my suggestion to that bug. It should probably be fixed in trunk, then merged to mediahandlers.
With those two fixes (application/ogg is audio, and the double-download bug), I'll accept the merge.
- 1797. By David Coles
-
Map 'application/ogg' to audio hander as recomended by XIPH. Note: Newer versions of Python's guess_type map '.ogg' to 'audio/ogg' anyway.
- 1798. By David Coles
-
Change handle_
content_ response so that we only fetch file contents inside-
fetch_text and not for all media types.Also remove present_editorhead since it's only useful action is an unreachable
codepath and requires threading some otherwise unneeded varaibles. - 1799. By David Coles
-
Add generic <object> media handler for suporting other media types (such as SVG)
- 1800. By David Coles
-
Use "?return=contents" URL instead of '/download/' since "Content-
Disposition:
attachment" causes issues with certain embeded media (mainly SVG).NB: The URLs generated do not support browsing revisions. See bug #610745.
David Coles (dcoles) wrote : | # |
Ok. Fixed both issues. The double-download fix is pretty much done as you described.
Also added a generic object handler to you can view SVG files (does not work with handle_image) and use ?return=content URLs for media rather than download URLs (with extra Content-Disposition headers). Still doesn't fix Chomium's issue with not playing ogg media from IVLE (will have to open a bug if this is merged as is).
Matt Giuca (mgiuca) wrote : | # |
I'd say open a bug on that last issue and merge as-is.
Just tried to merge; got 2 merge conflicts. Must have been with stuff Will has been doing today. Can you merge from trunk into mediahandlers, then I'll approve.
David Coles (dcoles) wrote : | # |
On 28 July 2010 17:22, Matt Giuca <email address hidden> wrote:
> Review: Needs Fixing
> I'd say open a bug on that last issue and merge as-is.
>
> Just tried to merge; got 2 merge conflicts. Must have been with stuff Will
> has been doing today. Can you merge from trunk into mediahandlers, then I'll
> approve.
> --
> https:/
> You proposed lp:~ivle-dev/ivle/mediahandlers for merging.
>
Right. I'll open the bug now - won't be able to merge until I get home (At
"work" waiting for people to head home).
- 1801. By David Coles
-
Merge from trunk
David Coles (dcoles) wrote : | # |
Resolved conflicts with trunk. Mostly just due to cjson->json changes.
Will merge into trunk.
Matt Giuca (mgiuca) wrote : | # |
I say you can merge too!
Preview Diff
1 | === modified file 'ivle/dispatch/request.py' |
2 | --- ivle/dispatch/request.py 2010-07-03 11:03:10 +0000 |
3 | +++ ivle/dispatch/request.py 2010-07-28 10:48:51 +0000 |
4 | @@ -95,6 +95,8 @@ |
5 | in class Request. |
6 | content_type (write) |
7 | String. The Content-Type (mime type) header value. |
8 | + content_length (write) |
9 | + Integer. The number of octets to be transfered. |
10 | location (write) |
11 | String. Response "Location" header value. Used with HTTP redirect |
12 | responses. |
13 | @@ -148,6 +150,7 @@ |
14 | # Default values for the output members |
15 | self.status = Request.HTTP_OK |
16 | self.content_type = None # Use Apache's default |
17 | + self.content_length = None # Don't provide Content-Length |
18 | self.location = None |
19 | # In some cases we don't want the template JS (such as the username |
20 | # and public FQDN) in the output HTML. In that case, set this to 0. |
21 | @@ -176,6 +179,8 @@ |
22 | # Prepare the HTTP and HTML headers before the first write is made |
23 | if self.content_type != None: |
24 | self.apache_req.content_type = self.content_type |
25 | + if self.content_length: |
26 | + self.apache_req.set_content_length(self.content_length) |
27 | self.apache_req.status = self.status |
28 | if self.location != None: |
29 | self.apache_req.headers_out['Location'] = self.location |
30 | |
31 | === modified file 'ivle/webapp/filesystem/browser/media/browser.css' |
32 | --- ivle/webapp/filesystem/browser/media/browser.css 2010-02-15 07:21:58 +0000 |
33 | +++ ivle/webapp/filesystem/browser/media/browser.css 2010-07-28 10:48:51 +0000 |
34 | @@ -47,6 +47,7 @@ |
35 | #actions { |
36 | padding: 0.3em 0.5em; |
37 | border-top: white 1px solid; /* Top edge of 3D effect */ |
38 | + line-height: 1.6em; |
39 | } |
40 | /* class "choice" is for all choices, enabled and disabled. This is for both |
41 | * the <a> actions and <option> actions. |
42 | |
43 | === modified file 'ivle/webapp/filesystem/browser/media/browser.js' |
44 | --- ivle/webapp/filesystem/browser/media/browser.js 2010-07-20 05:42:59 +0000 |
45 | +++ ivle/webapp/filesystem/browser/media/browser.js 2010-07-28 10:48:51 +0000 |
46 | @@ -31,6 +31,7 @@ |
47 | * "text" : When navigating to a text file, the text editor is opened. |
48 | * "image" : When navigating to an image, the image is displayed (rather than |
49 | * going to the text editor). |
50 | + * "video" : When navigation to a video file, a "play" button is presented. |
51 | * "audio" : When navigating to an audio file, a "play" button is presented. |
52 | * "binary" : When navigating to a binary file, offer it as a download through |
53 | * "serve". |
54 | @@ -43,7 +44,9 @@ |
55 | "application/x-javascript" : "text", |
56 | "application/javascript" : "text", |
57 | "application/json" : "text", |
58 | - "application/xml" : "text" |
59 | + "application/xml" : "text", |
60 | + "application/ogg" : "audio", |
61 | + "image/svg+xml": "object" |
62 | }; |
63 | |
64 | /* Mapping MIME types to icons, just the file's basename */ |
65 | @@ -193,7 +196,8 @@ |
66 | } |
67 | |
68 | /** Determines the "handler type" from a MIME type. |
69 | - * The handler type is a string, either "text", "image", "audio" or "binary". |
70 | + * The handler type is a string, either "text", "image", "video", "audio", |
71 | + * "object" or "binary". |
72 | */ |
73 | function get_handler_type(content_type) |
74 | { |
75 | @@ -205,7 +209,7 @@ |
76 | { /* Based on the first part of the MIME type */ |
77 | var handler_type = content_type.split('/')[0]; |
78 | if (handler_type != "text" && handler_type != "image" && |
79 | - handler_type != "audio") |
80 | + handler_type != "video" && handler_type != "audio") |
81 | handler_type = "binary"; |
82 | return handler_type; |
83 | } |
84 | @@ -329,46 +333,37 @@ |
85 | } |
86 | else |
87 | { |
88 | - /* Need to make a 2nd ajax call, this time get the actual file |
89 | - * contents */ |
90 | - callback = function(response) |
91 | - { |
92 | - /* Read the response and set up the page accordingly */ |
93 | - handle_contents_response(path, response); |
94 | - } |
95 | - /* Call the server and request the listing. */ |
96 | - if (url_args) |
97 | - args = shallow_clone_object(url_args); |
98 | - else |
99 | - args = {}; |
100 | - /* This time, get the contents of the file, not its metadata */ |
101 | - args['return'] = "contents"; |
102 | - ajax_call(callback, service_app, path, args, "GET"); |
103 | + /* Read the response and set up the page accordingly */ |
104 | + var content_type = current_file.type; |
105 | + handle_contents_response(path, content_type, url_args); |
106 | + |
107 | } |
108 | update_actions(isdir); |
109 | } |
110 | |
111 | -function handle_contents_response(path, response) |
112 | +function handle_contents_response(path, content_type) |
113 | { |
114 | /* Treat this as an ordinary file. Get the file type. */ |
115 | - var content_type = response.getResponseHeader("Content-Type"); |
116 | + //var content_type = response.getResponseHeader("Content-Type"); |
117 | var handler_type = get_handler_type(content_type); |
118 | - would_be_handler_type = handler_type; |
119 | /* handler_type should now be set to either |
120 | - * "text", "image", "audio" or "binary". */ |
121 | + * "text", "image", "video", "audio" or "binary". */ |
122 | switch (handler_type) |
123 | { |
124 | case "text": |
125 | - handle_text(path, response.responseText, |
126 | - would_be_handler_type); |
127 | + handle_text(path, content_type); |
128 | break; |
129 | case "image": |
130 | - /* TODO: Custom image handler */ |
131 | - handle_binary(path, response.responseText); |
132 | + handle_image(path); |
133 | + break; |
134 | + case "video": |
135 | + handle_video(path, content_type); |
136 | break; |
137 | case "audio": |
138 | - /* TODO: Custom audio handler */ |
139 | - handle_binary(path, response.responseText); |
140 | + handle_audio(path, content_type); |
141 | + break; |
142 | + case "object": |
143 | + handle_object(path, content_type); |
144 | break; |
145 | case "binary": |
146 | handle_binary(path); |
147 | @@ -540,6 +535,11 @@ |
148 | */ |
149 | function handle_binary(path) |
150 | { |
151 | + // Disable save button |
152 | + using_codepress = false; |
153 | + disable_save_if_safe(); |
154 | + |
155 | + // Show download link |
156 | var files = document.getElementById("filesbody"); |
157 | var div = document.createElement("div"); |
158 | files.appendChild(div); |
159 | @@ -554,6 +554,141 @@ |
160 | div.appendChild(par2); |
161 | } |
162 | |
163 | +/** Displays an image file. |
164 | + */ |
165 | +function handle_image(path) |
166 | +{ |
167 | + /* Disable save button */ |
168 | + using_codepress = false; |
169 | + disable_save_if_safe(); |
170 | + |
171 | + /* URL */ |
172 | + var url = app_url(service_app, path) + "?return=contents"; |
173 | + |
174 | + /* Image Preview */ |
175 | + var img = $("<img />"); |
176 | + img.attr("alt", path); |
177 | + img.attr("src", url); |
178 | + |
179 | + /* Show Preview */ |
180 | + var div = $('<div class="padding" />'); |
181 | + div.append('<h1>Image Preview</h1>'); |
182 | + div.append(img); |
183 | + $("#filesbody").append(div); |
184 | +} |
185 | + |
186 | +/** Displays a video. |
187 | + */ |
188 | +function handle_video(path, type) |
189 | +{ |
190 | + /* Disable save button and hide the save panel */ |
191 | + using_codepress = false; |
192 | + disable_save_if_safe(); |
193 | + |
194 | + /* URL */ |
195 | + var url = app_url(service_app, path) + "?return=contents"; |
196 | + var download_url = app_url(download_app, path); |
197 | + |
198 | + /* Fallback Download Link */ |
199 | + var link = $('<p>Could not display video in browser.<p><p><a /></p>'); |
200 | + var a = link.find('a'); |
201 | + a.attr("href", download_url); |
202 | + a.text("Download " + path); |
203 | + |
204 | + /* Fallback Object Tag */ |
205 | + var obj = $('<object />'); |
206 | + obj.attr("type", type); |
207 | + obj.attr("data", url); |
208 | + obj.append(link); |
209 | + |
210 | + /* HTML 5 Video Tag */ |
211 | + var video = $('<video controls="true" autoplay="true" />'); |
212 | + video.attr("src", url); |
213 | + var support = video[0].canPlayType && video[0].canPlayType(type); |
214 | + if (support != "probably" && support != "maybe") { |
215 | + // Use Fallback |
216 | + video = obj; |
217 | + } |
218 | + |
219 | + /* Show Preview */ |
220 | + var div = $('<div class="padding" />'); |
221 | + div.append('<h1>Video Preview</h1>'); |
222 | + div.append(video); |
223 | + $("#filesbody").append(div); |
224 | +} |
225 | + |
226 | +/** Display audio content |
227 | + */ |
228 | +function handle_audio(path, type) |
229 | +{ |
230 | + /* Disable save button and hide the save panel */ |
231 | + using_codepress = false; |
232 | + disable_save_if_safe(); |
233 | + |
234 | + /* URL */ |
235 | + var url = app_url(service_app, path) + "?return=contents"; |
236 | + var download_url = app_url(download_app, path); |
237 | + |
238 | + /* Fallback Download Link */ |
239 | + var link = $('<p>Could not display audio in browser.<p><p><a /></p>'); |
240 | + var a = link.find('a'); |
241 | + a.attr("href", download_url); |
242 | + a.text("Download " + path); |
243 | + |
244 | + /* Fallback Object Tag */ |
245 | + var obj = $('<object />'); |
246 | + obj.attr("type", type); |
247 | + obj.attr("data", url); |
248 | + obj.append(link); |
249 | + |
250 | + /* HTML 5 Audio Tag */ |
251 | + var audio = $('<audio controls="true" autoplay="true" />'); |
252 | + audio.attr("src", url); |
253 | + var support = audio[0].canPlayType && audio[0].canPlayType(type); |
254 | + if (support != "probably" && support != "maybe") { |
255 | + // Use Fallback |
256 | + audio = obj; |
257 | + } |
258 | + |
259 | + /* Show Preview */ |
260 | + var div = $('<div class="padding" />'); |
261 | + div.append('<h1>Audio Preview</h1>'); |
262 | + div.append(audio); |
263 | + $("#filesbody").append(div); |
264 | +} |
265 | + |
266 | +/** Display generic object content |
267 | + */ |
268 | +function handle_object(path, content_type) |
269 | +{ |
270 | + /* Disable save button and hide the save panel */ |
271 | + using_codepress = false; |
272 | + disable_save_if_safe(); |
273 | + |
274 | + /* URL */ |
275 | + var url = app_url(service_app, path) + "?return=contents"; |
276 | + var download_url = app_url(download_app, path); |
277 | + |
278 | + /* Fallback Download Link */ |
279 | + var link = $('<p><a /></p>'); |
280 | + var a = link.find('a'); |
281 | + a.attr("href", download_url); |
282 | + a.text("Download " + path); |
283 | + |
284 | + /* Object Tag */ |
285 | + var obj = $('<object width="100%" height="500px" />'); |
286 | + obj.attr("type", content_type); |
287 | + obj.attr("data", url); |
288 | + obj.append('Could not load object'); |
289 | + |
290 | + /* Show Preview */ |
291 | + var div = $('<div class="padding" />'); |
292 | + div.append('<h1>Preview</h1>'); |
293 | + div.append(obj); |
294 | + div.append(link); |
295 | + $("#filesbody").append(div); |
296 | +} |
297 | + |
298 | /* Enable or disable actions1 moreactions actions. Takes either a single |
299 | * name, or an array of them.*/ |
300 | function set_action_state(names, which, allow_on_revision) |
301 | @@ -841,11 +976,6 @@ |
302 | var moreactions = document.getElementById("moreactions_area"); |
303 | moreactions.setAttribute("style", "display: inline;"); |
304 | } |
305 | - else |
306 | - { |
307 | - var actions2_file = document.getElementById("actions2_file"); |
308 | - actions2_file.setAttribute("style", "display: inline;"); |
309 | - } |
310 | |
311 | return; |
312 | } |
313 | |
314 | === modified file 'ivle/webapp/filesystem/browser/media/editor.js' |
315 | --- ivle/webapp/filesystem/browser/media/editor.js 2010-07-28 04:12:08 +0000 |
316 | +++ ivle/webapp/filesystem/browser/media/editor.js 2010-07-28 10:48:51 +0000 |
317 | @@ -58,25 +58,6 @@ |
318 | window.onbeforeunload = confirm_beforeunload; |
319 | } |
320 | |
321 | -/** Presents the "editor heading" inserting it into a given element at |
322 | - * the front. Note that the save widget is handled by the Python. |
323 | - */ |
324 | -function present_editorhead(elem, path, handler_type) |
325 | -{ |
326 | - var div = document.getElementById("actions2"); |
327 | - |
328 | - /* Print a warning message if this is not actually a text file. |
329 | - */ |
330 | - if (handler_type != "text") |
331 | - { |
332 | - var warn = dom_make_text_elem("p", |
333 | - "Warning: You are editing a binary " + |
334 | - "file, which explains any strange characters you may see. If " + |
335 | - "you save this file, you could corrupt it."); |
336 | - div.appendChild(warn); |
337 | - } |
338 | -} |
339 | - |
340 | function highlighting_changed(select) |
341 | { |
342 | codemirror_language(select.value); |
343 | @@ -84,23 +65,40 @@ |
344 | |
345 | /** Presents the text editor. |
346 | */ |
347 | -function handle_text(path, text, handler_type) |
348 | +function handle_text(path, content_type, url_args) |
349 | +{ |
350 | + /* Need to make a 2nd ajax call, this time get the actual file |
351 | + * contents */ |
352 | + callback = function(response) |
353 | + { |
354 | + /* Read the response and set up the page accordingly */ |
355 | + handle_text_response(path, content_type, response.responseText); |
356 | + } |
357 | + /* Call the server and request the listing. */ |
358 | + if (url_args) |
359 | + args = shallow_clone_object(url_args); |
360 | + else |
361 | + args = {}; |
362 | + /* This time, get the contents of the file, not its metadata */ |
363 | + args['return'] = "contents"; |
364 | + ajax_call(callback, service_app, path, args, "GET"); |
365 | +} |
366 | + |
367 | +function handle_text_response(path, content_type, response_text) |
368 | { |
369 | /* Create a textarea with the text in it |
370 | * (The makings of a primitive editor). |
371 | */ |
372 | var files = document.getElementById("filesbody"); |
373 | - /* Put our UI at the top */ |
374 | - present_editorhead(files, path, handler_type); |
375 | |
376 | var div = document.createElement("div"); |
377 | div.style.height = '100%'; |
378 | files.appendChild(div); |
379 | var txt_elem = document.createElement("textarea"); |
380 | - txt_elem.value = text.toString(); |
381 | + txt_elem.value = response_text.toString(); |
382 | div.appendChild(txt_elem); |
383 | txt_elem.setAttribute("id", "editbox"); |
384 | - language = language_from_mime(current_file.type); |
385 | + language = language_from_mime(content_type); |
386 | |
387 | // Assume plaintext if no type can be determined. |
388 | language = language ? language : "text"; |
389 | @@ -176,6 +174,9 @@ |
390 | } else { |
391 | codemirror.setParser("DummyParser") |
392 | } |
393 | + |
394 | + // Show actions bar |
395 | + $("#actions2_file").show(); |
396 | } |
397 | |
398 | function language_from_mime(mime) |
399 | |
400 | === modified file 'ivle/webapp/filesystem/serve/__init__.py' |
401 | --- ivle/webapp/filesystem/serve/__init__.py 2010-07-28 05:06:15 +0000 |
402 | +++ ivle/webapp/filesystem/serve/__init__.py 2010-07-28 10:48:51 +0000 |
403 | @@ -139,6 +139,7 @@ |
404 | "attachment; filename=%s" % |
405 | response['name'].encode('utf-8')) |
406 | req.content_type = response['type'].encode('utf-8') |
407 | + req.content_length = response.get('size') |
408 | req.write(out) |
409 | |
410 | class DownloadView(ServeView): |
411 | |
412 | === modified file 'services/serveservice' |
413 | --- services/serveservice 2010-07-20 04:52:31 +0000 |
414 | +++ services/serveservice 2010-07-28 10:48:51 +0000 |
415 | @@ -37,7 +37,7 @@ |
416 | def determine_file_type(filename): |
417 | filetype = mimetypes.guess_type(filename)[0] |
418 | if filetype is None: |
419 | - filetype = ivle.mimetypes.DEFAULT_MIMETYPE |
420 | + filetype = ivle.mimetypes.DEFAULT_MIMETYPE |
421 | return filetype |
422 | |
423 | def throw_error(message, extra={}): |
424 | @@ -142,14 +142,18 @@ |
425 | zipmod.make_zip(zipbasepath, paths, zipfile) |
426 | |
427 | print json.dumps({'type': zip_mimetype, |
428 | - 'name': zipfilename.decode('utf-8')}) |
429 | + 'name': zipfilename.decode('utf-8'), |
430 | + 'size': len(zipfile.getvalue()), |
431 | + }) |
432 | |
433 | stream = zipfile |
434 | stream.seek(0) |
435 | else: |
436 | |
437 | print json.dumps({'type': determine_file_type(filename), |
438 | - 'name': os.path.basename(filename).decode('utf-8')}) |
439 | + 'name': os.path.basename(filename).decode('utf-8'), |
440 | + 'size': os.path.getsize(filename), |
441 | + }) |
442 | stream = open(filename) |
443 | |
444 | next = stream.read(1024) |
It looks fantastic! But I see some problems.
* The type handlers. Firstly, looking at the type_handlers dictionary in ivle/webapp/ filesystem/ browser/ media/browser. js, the dictionary only maps to "text" and "video" handlers. I'm a bit confused as to how audio and image handlers work (but I have seen them work, so ... please explain).
* Secondly, I'm not quite sure how the file extension maps onto the handler, but it seems to be a multi-stage process. First, the Python side maps the filename onto a MIME type, right? This would be something like "audio/ogg" or "video/ogg". Then our JavaScript maps the MIME type onto a handler type, like "video" or "audio". The problem is that if you have a ".ogg" file, Python will (I think) map it onto "audio/ogg", so our player will present an OGG Theora video with a ".ogg" extension as an audio file. I suppose there's nothing we can do about this except tell people to use ".ogv" instead. (So this is a non-issue, unless you can think of a better solution.)
* I'm seeing my test video with a length of 1 second, even though it continues playing for minutes. My test video is http:// people. xiph.org/ ~maikmerten/ demos/arctic_ giant.html -- maybe there's something wrong with the video. Can you offer any suggestions as to why this is? (I see that you added a content-length output to the download link, so that should be working fine. Could be related to the following problem.)
All of the above are permissible. I am rejecting this merge due to the following:
* The major problem: I'm seeing in Firebug that the video is being downloaded in full *twice*. I know that Mozilla Firefox downloads the video multiple times to do buffering, and Mozilla has told us that this doesn't actually equate to double the bandwidth as they cache it. But this is not that -- the browser actually downloads the full file from two separate URLs.
1. GET http:// ivle.localhost/ fileservice/ studenta/ stuff/arctic_ giant.ogv
Response content-type: text/plain
Response: {"listing": {".": {"isdir": false, "mtime_nice": "Tue Jul 27 2010, 11:58pm", "mtime_short": "Today", "mtime": 1280275123.6, "type": "video/ogg", "type_nice": "OGV file", "size": 23916003}}}
2. GET http:// ivle.localhost/ fileservice/ studenta/ stuff/arctic_ giant.ogv? return= contents
Response Content-Type: video/ogg
Response body: Full 22MB video
At this point, the video panel appears on the screen in the correct size, with a 1 second total time, and shows a loading throbber.
3. GET http:// ivle.localhost/ download/ studenta/ stuff/arctic_ giant.ogv
Response Content-Type: video/ogg
Response Content-Length: 23916003
Response body: Full 22MB video
Now the video starts playing.
So here's what's happening:
1. The JavaScript function "navigate" is called. This makes an AJAX call which is request #1 (GET /fileservice). This is a quick JSON response with the data for the file. Upon receiving the contents, it calls handle_response. return= contents) . "/* This time, get the contents of th...
2. The "handle_response" function is called with the JSON dictionary for the file. At the bottom of this function, for all files (but not directories), it makes a second AJAX call which is request #2 (GET /fileservice?