MJPEG для Opera

UserJS, добавляющий в Opera 12 воспроизведение потоков Motion JPEG.

// ==UserScript==
// @name        MJPEG+Opera - Add to Opera MJPEG support
// @author      Evgeny Stepanischev aka Bolk
// @version     1.00
// @namespace   http://bolknote.ru/files/opera-mjpeg.js
// @modified    2011-10-09
// @include     *
// ==/UserScript==

opera.addEventListener('BeforeEvent.DOMContentLoaded', function() {
	if (opera.version() < 12) return;

	var BolkMJpeg = function (url, img) {
		var req = new XMLHttpRequest(); 
		img.src = 'about:blank'; // Сброс картинки, чтобы прекратить передачу

		// Асинхронно забираем кадр при помощи XHR, нам придёт видеопоток
		var start = function (url) {
			req.open("GET", url, true);
			 // Свежепридуманный хитрый хак, позволяет получить данные в UCS-2
			req.overrideMimeType('image/jpeg');
			req.send(null);
		}

		// Хитрыми путями восстанавливаем порядок байт в reponse, там сейчас UCS-2,
		// получаем просто последовательность байт
		var reqbytes = function (n, l) {
			return escape(req.response.substr(n, l)).
				replace(/([^%]|%[^u].|%u....)/g, '<$1>').
				replace(/<%u(..)(..)>/g, '%$2%$1').
				replace(/<%(..)>/g, '%$1%00').
				replace(/<(.)>/g, '$1%00');
		}

		// заменяем картинку на бинарные данные
		var draw = function (bin) {
			img.src = 'data:image/jpeg,' + bin;
		}

		// На изменение состояние входных данных собираем по частям JPEG
		req.onreadystatechange = function () {
			// состояние «3» — данные пошли, но ещё не кончились
			if (req.readyState === 3) {
				var hnd = setInterval(function () {
					// по заголовку получаем размер данных
					var header = unescape(reqbytes(0, 300)).toLowerCase();

					var idx = header.indexOf('content-length:');

					if (idx > -1) {
						// 15 — длина заголовка
						var len = parseInt(header.substr(idx + 15), 10);
						var headend = header.indexOf("\r\n\r\n");

						// дожидаемся конца заголовка
						if (headend > -1) {
							var resp = reqbytes(0, len);
							// умножаем всё на три, так как reqbytes даёт esc-последовательность
							// вида %XX%XX…
							len *= 3;
							headend = (headend + 4) * 3;

							// если собрали весь JPEG, обрываем соединение
							// и начинаем новый цикл. Мы могли бы собирать кадры и дальше, но
							// это съест всю память со временем
							if (resp.length >= len + headend) {
								req.abort();
								resp = resp.substr(headend, len);

								draw(resp);
								clearInterval(hnd);

								start(url);
							}
						}
					}
				}, 100);
			}
		}

		start(url);
	};

	// ищем картинку, у которой расширение mjpg
	var imgs = document.getElementsByTagName('IMG');
	for (var i = 0, len = imgs.length; i<len; i++) {
		if (/\.mjpg$/.test(imgs[i].src)) {
			BolkMJpeg(imgs[i].src, imgs[i]);
			break;
		}
	}
}, true);