OSMF плагин для сохранения позиции воспроизведения видео

This post is also available in: Английский

В предыдущей статье мы писали о том, какие подходы можно использовать для сохранения позиции, на которой пользователь остановил просмотр видеоконтента. В этой статье рассмотрим более подробно один из способов, показавшийся нам наиболее интересным, — использование сокетов, — и покажем как написать и использовать плагин для существующего OSMF-плеера, который будет отправлять на сервер информацию о текущей позиции. Плагин, о котором идет речь в статье, со всеми исходными кодами и примерами вы можете скачать на нашем сайте labs.denivip.ru.

Способ сохранения позиции с использованием сокета представляется наиболее удобным исходя из следующих соображений. Он не требует установки Flash Media Server-а и загрузки видео через RTMP. Количество запросов на сервер минимизировано за счет поддержания одного, постоянно открытого в ходе воспроизведения видео, соединения. Отправки на сервер минимального количества информации (время по событиям начала воспроизведения, паузы и перемотки) достаточно для того, чтобы достаточно точно установить время остановки воспроизведения вне зависимости от причин, по которым она произошла: для этого достаточно лишь иметь время, позицию в видеофайле и тип последнего события и время обрыва соединения. Далее приведен несколько упрощенный код реализации такого подхода, не принимающий во внимание некоторые важные при реальном применении аспекты, но тем не менее демонстрирующий принципиальную возможность реализации выбранного подхода.

OSMF (Open Source Media Framework) — фреймворк от Adobe для создания Flash-медиаплееров на языке ActionScript. Его задача в том, чтобы предоставить разработчикам унифицированный, удобный, гибкий и модульный вариант использования возможностей по показу видеоконтента, которые существуют в платформе Flash. Необходимость такого фреймворка продиктована широким распространением онлайн-видео в сети, основной технологией для показа которого до сих пор является Flash. Хотя в последнее время предпринимаются попытки добавить аналогичную функциональность в стандартные возможности HTML, но пока эти разработки еще очень сырые и им не хватает некоторых критически важных для крупных коммерческих внедрений функций.

Перед началом разработки плагина потребуется выполнить следующие предварительные требования:

Плеер

Структура проекта OSMF-плеера

Сначала создадим простейший OSMF-плеер, и покажем как плагин подключается к плееру. Код этого плеера займет всего несколько строчек. Создаем спрайт, в котором будет отображаться видео и добавляем его на сцену. Загружаем плагин.

1
2
3
4
5
6
7
8
public function Main()
{
    sprite = new MediaPlayerSprite();
    addChild(sprite);
    factory = new DefaultMediaFactory();
    factory.addEventListener(MediaFactoryEvent.PLUGIN_LOAD, onPluginLoad);
    factory.loadPlugin(new PluginInfoResource(new StatsPluginInfo()));
}

После загрузки плагина, передаем полученный от него MediaElement в спрайт. Через метаданные ресурса передаем в плагин идентификатор пользователя, просматривающего контент.

1
2
3
4
5
6
7
8
private function onPluginLoad(event:MediaFactoryEvent):void
{
    var resource:URLResource = new URLResource("http://openx.denivip.ru/test-portal/video/omlet.f4v");
    resource.addMetadataValue(StatsPluginInfo.METADATA_USER_ID, 777);
    var media:MediaElement = factory.createMediaElement(resource);
    sprite.mediaPlayer.media = media;
    sprite.addEventListener(MouseEvent.CLICK, onMouseClick);
}

Плагин

Это все. Теперь нужно создать плагин. Для этого нужно создать новый Flex Library Project. Его структура представлена на скриншоте:

Структура проекта плагина

Исходные файлы плагина имеют следующее назначение:

Для того, чтобы фреймворк мог загрузить плагин, необходимо реализовать описание плагина, расширив стандартный класс PluginInfo. В его конструкторе мы задаем колбеки, которые будут вызваны для получения соответсвующей информации:

1
2
3
4
var items:Vector.<MediaFactoryItem> = new Vector.<MediaFactoryItem>();
var item:MediaFactoryItem = new MediaFactoryItem(NS, canHandleResourceFunction, mediaElementCreationFunction);
items.push(item);
super(items, creationNotificationFunction);

Метод canHandleResourceFunction возвращает true или false в зависимости от того, способен ли плагин работать с указанным типом ресурса. В нашем случае он всегда возвращает true.

В методе mediaElementCreationFunction мы можем возвратить в плеер собственный объект подкласс ProxyElement, который будет переопределять функциональность стандартного MediaElement. В нашем случае это не требуется, поэтому метод всегда возвращает объект класса VideoElement.

Метод creationNotificationFunction будет вызываться фреймворком при создании нового MediaElement-а, и именно здесь мы инициализируем код, который будет отслеживать позицию воспроизведения переданного MediaElement-а:

1
2
3
4
5
6
7
8
private function creationNotificationFunction(media:MediaElement):void
{
    trace('created media element');
    var tracker:StatsTracker = new StatsTracker(media);
    if (media.hasTrait(MediaTraitType.TIME)) {
        tracker.start();
    }
}

Рассмотрим код класса StatsTracker. При создании экземпляра этого класса мы создаем таймер, чтобы периодически получать текущую позицию, и задаем обработчики событий traitAdd и traitRemove, чтобы запускать и останавливать таймер в зависимости от наличия в MediaElement-е TimeTrait-а, в котором и содержится информация о текущей позиции.

1
2
3
4
5
6
7
8
9
10
11
public function StatsTracker(media:MediaElement)
{
    this.media = media;
    this.timer = new Timer(500);
    this.socket = new StatsSocket();
    timer.addEventListener(TimerEvent.TIMER, function (event:TimerEvent):void {
        checkPosition();
    });
    media.addEventListener(MediaElementEvent.TRAIT_ADD, onTraitAdd);
    media.addEventListener(MediaElementEvent.TRAIT_REMOVE, onTraitRemove);
}

Как только нужный нам Trait стал доступен, запускается таймер и происходит соединение с сервером:

1
2
3
4
5
6
public function start():void
{
    trace('start tracking');
    socket.connect(SERVER_HOST, SERVER_PORT);
    timer.start();
}

Если TimeTrait более недоступен, то сокет закрывается и таймер останавливается:

1
2
3
4
5
6
public function stop():void
{
    trace('do not track');
    socket.close();
    timer.stop();
}

Два раза в секунду мы отправляем через постоянно открытое соединение текущую позицию воспроизведения:

1
2
3
4
5
6
private function checkPosition():void
{
    var time:TimeTrait = media.getTrait(MediaTraitType.TIME) as TimeTrait;
    trace(time.currentTime);
    socket.writeln(time.currentTime.toString());
}

Серверная часть

Серверная часть реализована с использованием Node.js и представляет собой код, который просто выводит на консоль сообщения о начале и окончании соединения и полученные от плагина данные:

1
2
var net = require("net"),
    sys = require('sys');
1
2
3
4
5
6
7
8
9
10
11
12
13
net.createServer(function(socket) {
    socket.setEncoding("utf8");
    socket.on("connect", function() {
        sys.puts('client connected');
    });
    socket.on("data", function(data) {
        sys.puts(sys.inspect(data, false));
    });
    socket.on("end", function() {
        sys.puts('client disconnected');
        socket.end();
    });
}).listen(8125, "0.0.0.0");

Эта часть необходима, чтобы выдать Flash-у файл Cross-Domain Policy, который требуется для работы с сокетами.

1
2
3
4
5
6
7
8
net.createServer(function(socket) {
    socket.write("<?xml version=\"1.0\"?>\n");
    socket.write("<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n");
    socket.write("<cross-domain-policy>\n");
    socket.write("<allow-access-from domain=\"*\" to-ports=\"*\"/>\n");
    socket.write("</cross-domain-policy>\n");
    socket.end();
}).listen(843);

В итоге запустив плеер мы должны увидеть в консоли сервера сообщения, похожие на приведенные ниже:

1
2
3
4
5
6
7
8
9
10
client connected
...
'91.708'
'91.708'
'91.708'
'91.708'
'92.292'
'92.792'
'93.375'
client disconnected

Заключение

Представленный в этой статье код плагина и серверной части можно без труда расширить до полнофункционального решения, добавив отправку дополнительной информации о просмотре, такой как идентификатор видеоконтента, пользователя, возникающие в ходе воспроизведения события, и, добавив запись текущей позиции видеофайла в базу данных веб-сайта по обрыву соединения.

Обновлено Mon Jan 2 23:16:57 2017 +0300