PageSourceSearch

https://sungreen.github.io/src/assets.js

js sungreen.github.io collected 2026-10-03 10:00:22 UTC 28,409 bytes, 871 lines download raw bytes

1/**
2 * Copyright (C) 2014-2017 Triumph LLC
3 * 
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 * 
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 * 
14 * You should have received a copy of the GNU General Public License
15 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17"use strict";
18/**
19 * Low-level resource loader.
20 *
21 * NOTE: setup Nginx /etc/nginx/mime.types:
22 * audio/ogg ogg;
23 * to prevent 206 partial content response
24 *
25 * @name assets
26 * @namespace
27 * @exports exports as assets
28 */
29b4w.module["__assets"] = function(exports, require) {
30
31var m_cfg     = require("__config");
32var m_compat  = require("__compat");
33var m_pako    = require("__pako_inflate");
34var m_print   = require("__print");
35var m_sfg     = require("__sfx");
36var m_util    = require("__util");
37var m_version = require("__version");
38
39var cfg_def = m_cfg.defaults;
40var cfg_ldr = m_cfg.assets;
41
42// asset types
43exports.AT_ARRAYBUFFER       = 10;
44exports.AT_ARRAYBUFFER_ZIP   = 20;
45exports.AT_JSON              = 30;
46exports.AT_JSON_ZIP          = 40;
47exports.AT_TEXT              = 50;
48exports.AT_AUDIOBUFFER       = 60;
49exports.AT_IMAGE_ELEMENT     = 70;
50exports.AT_AUDIO_ELEMENT     = 80;
51exports.AT_VIDEO_ELEMENT     = 90;
52exports.AT_SEQ_VIDEO_ELEMENT = 100;
53
54// asset states: enqueued -> requested -> received
55var ASTATE_ENQUEUED = 10;
56var ASTATE_REQUESTED = 20;
57var ASTATE_RECEIVED = 30;
58var ASTATE_HALTED = 40;
59
60var _assets_queue = [];
61var _assets_pack_index = 0;
62
63// deprecated
64var _loaded_assets = {};
65
66var _arraybuffer_cache = {};
67var _img_cache = {};
68var _sound_cache = {};
69var _arraybuffer_sound_cache = {};
70
71function get_built_in_data() {
72    if (m_cfg.is_built_in_data())
73        return require(m_cfg.paths.built_in_data_module)["data"];
74    else
75        return null;
76}
77
78function FakeHttpRequest() {
79    var req = {
80        _source_url: null,
81        _parse_response: function(source) {
82            switch(req.responseType) {
83            case "json":
84            case "text":
85                return source;
86            case "arraybuffer":
87                var bin_str = atob(source);
88                var len = bin_str.length;
89
90                var arr_buffer = new Int8Array(len);
91                for (var i = 0; i < len; i++)
92                    arr_buffer[i] = bin_str.charCodeAt(i);
93                return arr_buffer.buffer;
94            default:
95                return source;
96            }
97        },
98
99        status: 0,
100        readyState: 0,
101        response: "",
102        responseType: "",
103        onreadystatechange: null,
104
105        overrideMimeType: function() {},
106        addEventListener: function() {},
107        open: function(method, url, async) {
108            req._source_url = url;
109            req.readyState = 1;
110        },
111        send: function() {
112            req.status = 404;
113            req.readyState = 4;
114            var bd = get_built_in_data();
115            if (bd && req._source_url in bd) {
116                req.status = 200;
117                if (bd[req._source_url])
118                    req.response = req._parse_response(bd[req._source_url]);
119            }
120            var get_type = {};
121            if (get_type.toString.call(req.onreadystatechange)
122                    === '[object Function]')
123                req.onreadystatechange();
124        }
125    }
126    return req;
127}
128
129/**
130 * Split path to head and extension: "123.txt" -> ["123", "txt"]
131 */
132exports.split_extension = function(path) {
133    var dot_split = path.split(".");
134
135    var head_ext = Array(2);
136
137    head_ext[0] = dot_split.slice(0, -1).join(".");
138    head_ext[1] = String(dot_split.slice(-1));
139
140    return head_ext;
141}
142
143/**
144 * Get text by sync request.
145 * @deprecated Any usage is strongly discouraged
146 */
147exports.get_text_sync = function(asset_uri) {
148    // check in cache
149    if (_loaded_assets[asset_uri])
150        return _loaded_assets[asset_uri];
151
152    if (cfg_ldr.prevent_caching)
153        var filepath = asset_uri + m_version.timestamp();
154    else
155        var filepath = asset_uri;
156
157    var req = new XMLHttpRequest();
158    req.overrideMimeType("text/plain"); // to prevent "not well formed" error
159    req.open("GET", filepath, false);
160    req.send(null);
161
162    if (req.status == 200 || req.status == 0) {
163        var resp_text = req.responseText;
164        if (resp_text.length) {
165            // save in cache
166            _loaded_assets[asset_uri] = resp_text;
167            return resp_text;
168        } else
169            m_util.panic("Error XHR: responce is empty, GET " + asset_uri);
170    } else {
171        m_util.panic("Error XHR: " + req.status + ", GET " + asset_uri);
172    }
173}
174
175exports.cleanup = function() {
176    for (var i = 0; i < _assets_queue.length; i++)
177        _assets_queue[i].state = ASTATE_HALTED;
178    _assets_queue = [];
179    _assets_pack_index = 0;
180
181    // deprecated
182    _loaded_assets = {};
183}
184
185exports.enqueue = function(assets_pack, asset_cb, pack_cb, progress_cb, json_reviver) {
186    for (var i = 0; i < assets_pack.length; i++) {
187        var elem = assets_pack[i];
188
189        var asset = {
190            id: elem.id,
191            type: elem.type,
192            url: elem.url,
193            is_fetch: elem.is_fetch,
194            request_method: elem.request_method ? elem.request_method : "GET",
195            overwrite_header: elem.overwrite_header ? elem.overwrite_header : null,
196            post_data: elem.post_data ? elem.post_data : null,
197            param: elem.param ? elem.param : null,
198
199            state: ASTATE_ENQUEUED,
200
201            asset_cb: asset_cb || (function() {}),
202            pack_cb: pack_cb || (function() {}),
203            progress_cb: progress_cb || (function() {}),
204            json_reviver: json_reviver || null,
205
206            pack_index: _assets_pack_index
207        }
208
209        if (cfg_ldr.prevent_caching) {
210            var bd = get_built_in_data();
211            if (!(bd && asset.url in bd))
212                asset.url += m_version.timestamp();
213        }
214
215        _assets_queue.push(asset);
216    }
217
218    request_assets(_assets_queue);
219    _assets_pack_index++;
220}
221
222/**
223 * Executed every frame
224 */
225exports.update = function() {
226    request_assets(_assets_queue);
227    handle_packs(_assets_queue);
228}
229
230function request_assets(queue) {
231
232    var req_cnt = 0;
233
234    for (var i = 0; i < queue.length; i++) {
235        var asset = queue[i];
236
237        if (asset.state === ASTATE_REQUESTED)
238            req_cnt++;
239
240        // check requests limit
241        if (req_cnt >= cfg_ldr.max_requests)
242            break;
243
244        // pass recently enqueued
245        if (asset.state !== ASTATE_ENQUEUED)
246            continue;
247
248        asset.state = ASTATE_REQUESTED;
249        req_cnt++;
250
251        switch (asset.type) {
252        case exports.AT_JSON_ZIP:
253        case exports.AT_ARRAYBUFFER:
254        case exports.AT_ARRAYBUFFER_ZIP:
255            request_arraybuffer(asset, "arraybuffer");
256            break;
257        case exports.AT_JSON:
258            request_arraybuffer(asset, "json");
259            break;
260        case exports.AT_TEXT:
261            request_arraybuffer(asset, "text");
262            break;
263        case exports.AT_AUDIOBUFFER:
264            request_audiobuffer(asset);
265            break;
266        case exports.AT_IMAGE_ELEMENT:
267            request_image(asset);
268            break;
269        case exports.AT_AUDIO_ELEMENT:
270            request_audio(asset);
271            break;
272        case exports.AT_VIDEO_ELEMENT:
273            request_video(asset);
274            break;
275        case exports.AT_SEQ_VIDEO_ELEMENT:
276            request_seq_video(asset);
277            break;
278        default:
279            m_util.panic("Wrong asset type: " + asset.type);
280            break;
281        }
282    }
283}
284
285function request_arraybuffer(asset, response_type) {
286    var filepath = asset.url.split("?v=")[0];
287    if (filepath in _arraybuffer_cache) {
288        if (response_type == "json")
289            asset.asset_cb(m_util.clone_object_r(_arraybuffer_cache[filepath]), asset.id, asset.type,
290                    asset.url, asset.param);
291        else
292            asset.asset_cb(_arraybuffer_cache[filepath], asset.id, asset.type,
293                    asset.url, asset.param);
294        return;
295    }
296    var bd = get_built_in_data();
297    if (bd && asset.url in bd)
298        var req = new FakeHttpRequest();
299    else
300        var req = new XMLHttpRequest();
301
302    var content_type = null;
303    if (asset.request_method == "GET") {
304        req.open("GET", asset.url, true);
305    } else if (asset.request_method == "POST") {
306        req.open("POST", asset.url, true);
307        switch (asset.type) {
308        case exports.AT_TEXT:
309            content_type = 'text/plain';
310            break;
311        case exports.AT_JSON:
312            content_type = 'application/json';
313            break;
314        }
315    }
316
317    if (asset.overwrite_header) {
318        for (var key in asset.overwrite_header) {
319            if (key == "Content-Type")
320                content_type = asset.overwrite_header[key];
321            else
322                req.setRequestHeader(key, asset.overwrite_header[key]);
323        }
324    }
325
326    if (content_type)
327        req.setRequestHeader("Content-Type", content_type);
328
329    if (response_type == "text") {
330        // to prevent "not well formed" error (GLSL)
331        req.overrideMimeType("text/plain");
332        req.responseType = "text";
333    } else if (response_type == "json") {
334        // NOTE: workaround, json response type not implemented in some browsers
335        //m_print.log("Apply json load workaround");
336        req.overrideMimeType("application/json");
337        req.responseType = "text";
338    } else
339        req.responseType = response_type;
340
341    req.onreadystatechange = function() {
342        if (asset.state != ASTATE_HALTED)
343            if (req.readyState == 4) {
344                if (req.status == 200 || req.status == 0) {
345                    var response = req.response;
346                    var empty_response = !response
347                        || (response_type == "arraybuffer" && response["byteLength"] == 0)
348                    if (!empty_response) {
349
350                        switch (asset.type) {
351                        case exports.AT_JSON_ZIP:
352                            try {
353                                response = m_pako.inflate(response, { to: "string" });
354                            } catch(e) {
355                                m_print.error(e + " (parsing gzipped file " + asset.url + ")");
356                                asset.asset_cb(null, asset.id, asset.type, asset.url, asset.param);
357                                return;
358                            }
359                        case exports.AT_JSON:
360                            try {
361                                response = JSON.parse(response, asset.json_reviver);
362                            } catch(e) {
363                                m_print.error(e + " (parsing JSON " + asset.url + ")");
364                                asset.asset_cb(null, asset.id, asset.type, asset.url, asset.param);
365                                return;
366                            }
367                            break;
368                        case exports.AT_ARRAYBUFFER_ZIP:
369                            try {
370                                response = m_pako.inflate(response).buffer;
371                            } catch(e) {
372                                m_print.error(e + " (parsing gzipped file " + asset.url + ")");
373                                asset.asset_cb(null, asset.id, asset.type, asset.url, asset.param);
374                                return;
375                            }
376                            break;
377                        }
378
379                        if (asset.is_fetch)
380                            if (asset.type == exports.AT_JSON || asset.type == exports.AT_JSON_ZIP)
381                                _arraybuffer_cache[filepath] = m_util.clone_object_r(response);
382                            else
383                                _arraybuffer_cache[filepath] = response;
384
385                        asset.asset_cb(response, asset.id, asset.type, asset.url, asset.param);
386                    } else {
387                        m_print.error("empty responce when trying to get " + asset.url);
388                        asset.asset_cb(null, asset.id, asset.type, asset.url, asset.param);
389                    }
390                } else {
391                    m_print.error(req.status + " when trying to get " + asset.url);
392                    asset.asset_cb(null, asset.id, asset.type, asset.url, asset.param);
393                }
394                asset.state = ASTATE_RECEIVED;
395            }
396    };
397
398    req.addEventListener("progress", function(e) {
399        // compute progress information if total size is known
400        if (e.lengthComputable)
401            asset.progress_cb(e.loaded / e.total);
402    }, false);
403
404    req.send(asset.post_data);
405}
406
407function request_audiobuffer(asset) {
408    var filepath = asset.url.split("?v=")[0];
409    if (filepath in _arraybuffer_sound_cache) {
410        asset.asset_cb(_arraybuffer_sound_cache[filepath], asset.id, asset.type,
411                asset.url, asset.param);
412        asset.state = ASTATE_RECEIVED;
413        return;
414    }
415    if (asset.request_method != "GET") {
416        m_util.panic("Unsupported request type for audio buffer");
417    }
418    var bd = get_built_in_data();
419    if (bd && asset.url in bd)
420        var req = new FakeHttpRequest();
421    else
422        var req = new XMLHttpRequest();
423
424    req.open("GET", asset.url, true);
425
426    req.responseType = "arraybuffer";
427
428    req.onreadystatechange = function() {
429        if (asset.state != ASTATE_HALTED)
430            if (req.readyState == 4) {
431                if (req.status == 200 || req.status == 0) {
432                    var response = req.response;
433                    if (response) {
434                        var decode_cb = function(audio_buffer) {
435                            asset.asset_cb(audio_buffer, asset.id, asset.type, asset.url, asset.param);
436                            asset.state = ASTATE_RECEIVED;
437                            if (asset.is_fetch) {
438                                var filepath = asset.url.split("?v=")[0];
439                                _arraybuffer_sound_cache[filepath] = audio_buffer;
440                            }
441                        }
442                        var fail_cb = function() {
443                            m_print.error("failed to decode " + asset.url);
444                            asset.asset_cb(null, asset.id, asset.type, asset.url, asset.param);
445                            asset.state = ASTATE_RECEIVED;
446                        }
447
448                        m_sfg.decode_audio_data(response, decode_cb, fail_cb);
449
450                    } else {
451                        m_print.error("empty responce when trying to get " + asset.url);
452                        asset.asset_cb(null, asset.id, asset.type, asset.url, asset.param);
453                        asset.state = ASTATE_RECEIVED;
454                    }
455                } else {
456                    m_print.error(req.status + " when trying to get " + asset.url);
457                    asset.asset_cb(null, asset.id, asset.type, asset.url, asset.param);
458                    asset.state = ASTATE_RECEIVED;
459                }
460            }
461    };
462
463    req.send();
464}
465
466function request_image(asset) {
467    var filepath = asset.url.split("?v=")[0];
468    if (filepath in _img_cache) {
469        asset.asset_cb(_img_cache[filepath], asset.id, asset.type, asset.url, asset.param);
470        asset.state = ASTATE_RECEIVED;
471        return;
472    }
473    if (asset.request_method != "GET") {
474        m_util.panic("Unsupported request type for image element");
475    }
476    var image = document.createElement("img");
477    if (cfg_def.allow_cors)
478        image.crossOrigin = "Anonymous";
479    image.onload = function() {
480        if (asset.state != ASTATE_HALTED) {
481            asset.asset_cb(image, asset.id, asset.type, asset.url, asset.param);
482            asset.state = ASTATE_RECEIVED;
483            if (asset.is_fetch) {
484                var filepath = asset.url.split("?v=")[0];
485                _img_cache[filepath] = image;
486            }
487        }
488    };
489    image.addEventListener("error", function() {
490        if (asset.state != ASTATE_HALTED) {
491            m_print.error("could not load image: " + asset.url);
492            asset.asset_cb(null, asset.id, asset.type, asset.url, asset.param);
493            asset.state = ASTATE_RECEIVED;
494        }
495    }, false);
496
497    var bd = get_built_in_data();
498    if (bd && asset.url in bd) {
499        if (bd[asset.url]) {
500            var img_mime_type = get_image_mime_type(asset.url);
501            image.src = "data:" + img_mime_type + ";base64," + bd[asset.url];
502        } else {
503            if (m_compat.is_ie11()) {
504                var e = document.createEvent("CustomEvent");
505                e.initCustomEvent("error", false, false, null);
506            } else
507                var e = new CustomEvent("error");
508            image.dispatchEvent(e);
509        }
510    } else
511        image.src = asset.url;
512}
513
514function request_audio(asset) {
515    var filepath = asset.url.split("?v=")[0];
516    if (filepath in _sound_cache) {
517        asset.asset_cb(_sound_cache[filepath], asset.id, asset.type,
518                asset.url, asset.param);
519        asset.state = ASTATE_RECEIVED;
520        return;
521    }
522    if (asset.request_method != "GET") {
523        m_util.panic("Unsupported request type for audio element");
524    }
525    var audio = document.createElement("audio");
526    if (cfg_def.allow_cors)
527        audio.crossOrigin = "Anonymous";
528
529    audio.addEventListener("loadeddata", function() {
530        if (asset.state != ASTATE_HALTED) {
531            asset.asset_cb(audio, asset.id, asset.type, asset.url, asset.param);
532            asset.state = ASTATE_RECEIVED;
533            if (asset.is_fetch) {
534                var filepath = asset.url.split("?v=")[0];
535                _sound_cache[filepath] = audio;
536            }
537        }
538    }, false);
539
540    audio.addEventListener("error", function() {
541        if (asset.state != ASTATE_HALTED) {
542            m_print.error("could not load sound: " + asset.url);
543            asset.asset_cb(null, asset.id, asset.type, asset.url, asset.param);
544            asset.state = ASTATE_RECEIVED;
545        }
546    }, false);
547
548    audio.addEventListener("stalled", function() {
549        if (asset.state != ASTATE_HALTED) {
550            m_print.error("could not load sound: " + asset.url);
551            asset.asset_cb(null, asset.id, asset.type, asset.url, asset.param);
552            asset.state = ASTATE_RECEIVED;
553        }
554    }, false);
555
556    var bd = get_built_in_data();
557    if (bd && asset.url in bd) {
558        if (bd[asset.url]) {
559            var snd_mime_type = get_sound_mime_type(asset.url);
560            audio.src = "data:" + snd_mime_type + ";base64," + bd[asset.url];
561            if (asset.state != ASTATE_HALTED) {
562                asset.asset_cb(audio, asset.id, asset.type, asset.url, asset.param);
563                asset.state = ASTATE_RECEIVED;
564            }
565
566        } else {
567            if (m_compat.is_ie11()) {
568                var e = document.createEvent("CustomEvent");
569                e.initCustomEvent("error", false, false, null);
570            } else
571                var e = new CustomEvent("error");
572            audio.dispatchEvent(e);
573        }
574    } else {
575        audio.src = asset.url;
576        if (cfg_def.is_mobile_device)
577            audio.load();
578    }
579
580    if (cfg_def.mobile_firefox_media_hack) {
581        audio.autoplay = true;
582        audio.pause();
583    }
584
585    // HACK: workaround for some garbage collector bug
586    setTimeout(function() {audio.some_prop_to_prevent_gc = 1}, 5000);
587}
588
589function request_video(asset) {
590    var filepath = asset.url.split("?v=")[0];
591    if (filepath in _img_cache) {
592        asset.asset_cb(_img_cache[filepath], asset.id, asset.type, asset.url, asset.param);
593        asset.state = ASTATE_RECEIVED;
594        return;
595    }
596    if (asset.request_method != "GET") {
597        m_util.panic("Unsupported request type for video element");
598    }
599    var video = document.createElement("video");
600    video.muted = true;
601    // HACK: allow crossOrigin for mobile devices (Android Chrome bug)
602    if (cfg_def.allow_cors)
603        video.crossOrigin = "Anonymous";
604    video.addEventListener("loadeddata", function() {
605        video.removeEventListener("error", video_error_event, false);
606        if (asset.state != ASTATE_HALTED) {
607            asset.asset_cb(video, asset.id, asset.type, asset.url, asset.param);
608            asset.state = ASTATE_RECEIVED;
609            if (asset.is_fetch) {
610                var filepath = asset.url.split("?v=")[0];
611                _img_cache[filepath] = video;
612            }
613        }
614    }, false);
615
616    function video_error_event(e) {
617        if (asset.state != ASTATE_HALTED) {
618            m_print.error("could not load video: " + asset.url, asset.param);
619            asset.asset_cb(null, asset.id, asset.type, asset.url);
620            asset.state = ASTATE_RECEIVED;
621        }
622    }
623    video.addEventListener("error", video_error_event, false);
624
625    var bd = get_built_in_data();
626    if (bd && asset.url in bd) {
627        if (bd[asset.url]) {
628            var vid_mime_type = get_video_mime_type(asset.url);
629            video.src = "data:" + vid_mime_type + ";base64," + bd[asset.url];
630            if (asset.state != ASTATE_HALTED)
631                video.addEventListener("loadeddata", function() {
632                    asset.asset_cb(video, asset.id, asset.type, asset.url, asset.param);
633                    asset.state = ASTATE_RECEIVED;
634                }, false);
635        } else {
636            if (m_compat.is_ie11()) {
637                var e = document.createEvent("CustomEvent");
638                e.initCustomEvent("error", false, false, null);
639            } else
640                var e = new CustomEvent("error");
641            video.dispatchEvent(e);
642        }
643    } else {
644        video.src = asset.url;
645        if (cfg_def.is_mobile_device)
646            video.load();
647    }
648
649    if (cfg_def.mobile_firefox_media_hack) {
650        video.autoplay = true;
651        video.pause();
652    }
653
654    // HACK: workaround for some garbage collector bug
655    setTimeout(function() {video.some_prop_to_prevent_gc = 1}, 10000);
656}
657
658function request_seq_video(asset) {
659    var filepath = asset.url.split("?v=")[0];
660    if (filepath in _img_cache) {
661        asset.asset_cb(_img_cache[filepath], asset.id, asset.type, asset.url, asset.param);
662        asset.state = ASTATE_RECEIVED;
663        return;
664    }
665    if (asset.request_method != "GET") {
666        m_util.panic("Unsupported request type for seq video element");
667    }
668    var bd = get_built_in_data();
669    if (bd && asset.url in bd)
670        var req = new FakeHttpRequest();
671    else
672        var req = new XMLHttpRequest();
673
674    req.open("GET", asset.url, true);
675    req.responseType = "arraybuffer";
676
677    function load_cb(images) {
678        asset.asset_cb(images, asset.id, asset.type, asset.url, asset.param);
679        asset.state = ASTATE_RECEIVED;
680        if (asset.is_fetch) {
681            var filepath = asset.url.split("?v=")[0];
682            _img_cache[filepath] = images;
683        }
684    }
685
686    req.onreadystatechange = function() {
687    if (asset.state != ASTATE_HALTED)
688        if (req.readyState == 4) {
689            if (req.status == 200 || req.status == 0) {
690                var response = req.response;
691                if (response)
692                    parse_seq_video_file(response, load_cb);
693                else {
694                    m_print.error("empty responce when trying to get " + asset.url);
695                    asset.asset_cb(null, asset.id, asset.type, asset.url, asset.param);
696                    asset.state = ASTATE_RECEIVED;
697                }
698            } else {
699                m_print.error(req.status + " when trying to get " + asset.url);
700                asset.asset_cb(null, asset.id, asset.type, asset.url, asset.param);
701                asset.state = ASTATE_RECEIVED;
702            }
703        }
704    };
705    req.addEventListener("progress", function(e) {
706        // compute progress information if total size is known
707        if (e.lengthComputable)
708            asset.progress_cb(e.loaded / e.total);
709    }, false);
710
711    req.send();
712}
713
714function parse_seq_video_file(response, callback) {
715    var buffer = new Int32Array(response);
716    var seq_image_data = new Int8Array(response);
717    var number = buffer[3];
718    var data = {
719        images: [],
720        blobs: [],
721        fps: buffer[4]
722    };
723    var offset = 20;
724    for (var j = 0; j < number; j++) {
725        var size = buffer[offset/4];
726        var frame = seq_image_data.subarray(offset + 4, offset + 4 + size);
727        var blob = new Blob([frame], {type: "image/jpg"});
728        var image = document.createElement("img");
729        image.src = window.URL.createObjectURL(blob);
730        data.images.push(image);
731        // NOTE: IE HTML7007 message hack
732        data.blobs.push(blob);
733        offset +=size + 8 - size % 4;
734    }
735    // NOTE: wait for loading last image
736    image.onload = function() {
737        for (var i = 0; i < data.images.length; i++)
738            window.URL.revokeObjectURL(data.images[i].src);
739        delete data.blobs;
740        callback(data);
741    }
742}
743
744function get_image_mime_type(file_path) {
745    var ext = m_util.get_file_extension(file_path);
746    var mime_type = "image";
747    switch(ext.toLowerCase()) {
748    case "jpeg":
749    case "jpg":
750        mime_type += "/jpeg";
751        break;
752    case "png":
753        mime_type += "/png";
754        break;
755    }
756
757    return mime_type;
758}
759
760function get_sound_mime_type(file_path) {
761    var ext = m_util.get_file_extension(file_path);
762    var mime_type = "audio";
763    switch(ext.toLowerCase()) {
764    case "ogv":
765    case "ogg":
766        mime_type += "/ogg";
767        break;
768    case "mp3":
769        mime_type += "/mpeg";
770        break;
771    case "m4v":
772    case "mp4":
773        mime_type += "/mp4";
774        break;
775    case "webm":
776        mime_type += "/webm";
777        break;
778    }
779
780    return mime_type;
781}
782
783function get_video_mime_type(file_path) {
784    var ext = m_util.get_file_extension(file_path);
785    var mime_type = "video";
786    switch(ext.toLowerCase()) {
787    case "ogv":
788        mime_type += "/ogg";
789        break;
790    case "webm":
791        mime_type += "/webm";
792        break;
793    case "m4v":
794        mime_type += "/mp4";
795        break;
796    }
797
798    return mime_type;
799}
800
801exports.check_image_extension = function(ext) {
802    if (ext == "png"
803            || ext == "jpg"
804            || ext == "jpeg"
805            || ext == "gif"
806            || ext == "bmp"
807            || ext == "dds"
808            || ext == "pvr")
809        return true;
810
811    return false;
812}
813
814/**
815 * Find loaded packs, exec callback and remove from queue
816 */
817function handle_packs(queue) {
818
819    var pack_first_index = 0;
820    var pack_cb_exec = true;
821
822    for (var i = 0; i < queue.length; i++) {
823        var asset = queue[i];
824        var asset_pack_first = queue[pack_first_index];
825
826        if (asset.pack_index === asset_pack_first.pack_index) {
827            if (asset.state !== ASTATE_RECEIVED)
828                pack_cb_exec = false;
829        } else {
830            if (pack_cb_exec) {
831                queue[i-1].pack_cb();
832                var spliced_count = i-pack_first_index;
833                queue.splice(pack_first_index, spliced_count);
834                i-=spliced_count;
835            }
836
837            pack_first_index = i;
838            pack_cb_exec = (queue[i].state === ASTATE_RECEIVED) ? true : false;
839        }
840
841        if ((i === (queue.length-1)) && pack_cb_exec) {
842            var last_asset = queue[i];
843            queue.splice(pack_first_index);
844            // Should be executed after splice. Possible enqueue in pack_cb
845            last_asset.pack_cb();
846        }
847    }
848}
849
850function debug_queue(queue, opt_log_prefix) {
851    var state_str = "[";
852
853    for (var i = 0; i < queue.length; i++)
854        state_str += String(queue[i].state / 10);
855
856    state_str += "]";
857
858    if (opt_log_prefix || opt_log_prefix === 0)
859        m_print.log(opt_log_prefix, state_str);
860    else
861        m_print.log(state_str);
862}
863
864exports.clear_cache = function() {
865    _arraybuffer_cache = {};
866    _img_cache = {};
867    _sound_cache = {};
868    _arraybuffer_sound_cache = {};
869}
870
871}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.