Статьи

Создание тестера пропускной способности для загрузки видео во Flash с AS3

В веб-разработке и обслуживании медиа-контента вам часто требуется способ обеспечить пользователю плавный просмотр. Для пользователей с небольшой пропускной способностью и медленным интернет-соединением вам нужно обслуживать видео меньшего размера, чем кому-либо по соединению T1. Из этого туториала вы узнаете, как определить пропускную способность пользователя и предоставить ему соответствующее Flash-видео. Мы создадим интерфейс, подобный селектору качества YouTube, и немного узнаем об использовании пользовательских событий и повторно используемых классов.


Мы собираемся создать класс BandwidthTester, который позаботится об измерении пропускной способности. Затем мы будем использовать этот класс в плеере для загрузки определенного фильма. Класс не такой сложный; мы загрузим тестовый файл и измерим, сколько байтов было загружено каждую секунду, а затем используем эти данные, чтобы получить среднюю скорость.

Давайте начнем. Сначала создайте папку, в которую мы поместим все источники. В последней загрузке исходного файла я предоставил папку с videos в которой находятся три FLV-файла.

В основной папке создайте файл с именем BandwidthTester.as и введите следующее:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package {
    import flash.net.*;
    import flash.utils.*;
    import flash.events.*;
     
    public class BandwidthTester extends EventDispatcher {
        public static const BAND_TESTED = ‘tested’;
        public static const TEST = ‘test’;
         
        private var bandwidth = 0;
        private var peak_bandwidth = 0;
        private var curr_bandwidth = 0;
         
        private var testfile = ‘videos/video_hq.flv’;
        private var l;
        private var tm;
        private var last_bytes = 0;
        private var bands;
        private var _latency = 1;
         
         
        public function BandwidthTester( latency=0 ){
            trace(‘bandwidth tester loaded.’);
        }
         
    }
}

Здесь я создал пакет, импортировал необходимые пакеты и создал основной класс BandwidthTester. Вы заметите, что класс расширяет EventDispatcher. Это необходимо для отправки событий классам, которые подписываются. Я создал пару переменных для хранения различных элементов: две константы будут содержать два типа событий; переменная пропускная способность будет содержать конечную среднюю скорость; peak_bandwidth будет удерживать пиковую скорость (самое высокое измеренное значение); и curr_bandwidth будет использоваться в событии TIMER для хранения текущей измеренной скорости.

Я определил переменную testfile которая указывает на видео самого большого размера. В идеале вы хотели бы установить для этого файла размером более 1 МБ в Интернете, который не изменяется, например, библиотеку Google CDN или что-то еще. Я также определил переменные для хранения URLLoader, таймера, переменную, которая содержит загруженный last_bytes, переменную bands которая будет содержать все измерения, и свойство, называемое _latency которое будет использоваться как приближение сетевых издержек.


Давайте сделаем основной функционал тестера пропускной способности. Замените содержимое функции конструктора следующим:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
public function BandwidthTester( latency=0 ){
    tm = new Timer(1000, 3);
    tm.addEventListener( TimerEvent.TIMER, get_band );
    tm.addEventListener( TimerEvent.TIMER_COMPLETE, timer_complete );
    bands = new Array();
    _latency = 1-latency;
}
     
public function start(){
    l = new URLLoader();
    l.addEventListener( Event.OPEN, start_timer );
    l.addEventListener( Event.COMPLETE, end_download );
    l.load( new URLRequest( testfile ) );
}
     
public function start_timer( e:Event ){
    tm.start();
}

Я устанавливаю таймер с задержкой в ​​1 секунду и 3 повтора. Это гарантирует, что у нас будет три события таймера на расстоянии одной секунды во времени. Мы будем использовать get_band() для сохранения загруженных байтов. Я также инициализирую массив bands и устанавливаю задержку из аргументов конструктора. Все тестирование не начинается, пока я не вызову метод start() , который создает URLLoader и загружает тестовый файл после добавления прослушивателей событий в события OPEN и COMPLETE. Когда файл начал загрузку, мы запускаем таймер.


Нам нужно сделать get_band() :

1
2
3
4
5
6
7
private function get_band( e:TimerEvent ){
    curr_bandwidth = Math.floor(((l.bytesLoaded-last_bytes) /125) * _latency);
    bands.push( curr_bandwidth );
    last_bytes = l.bytesLoaded;
         
    dispatchEvent( new Event( BandwidthTester.TEST ) );
}

Это сердце измерительного кода. Мы вычисляем текущую скорость после первой секунды, вычитая last_bytes из свойства bytesLoaded объекта urlloader, умноженного на задержку. Задержка — это число от 0 до 1, поэтому, когда мы устанавливаем, например, приближение сетевой нагрузки на 0,2, скорость будет умножаться на 0,8, то есть на 80 процентов от скорости.

Я также делю вычтенные байты на 125, чтобы получить число килобит / секунду. В сети скорость измеряется как количество килобит в секунду (кБ / с), а 1 килобит равен 125 байтов. Когда сети рекламируют 1 Мбит / с, они обычно означают 1 мегабит / с, что означает, что максимальная скорость загрузки будет 125 килобайт / с. Я использую Math.floor() чтобы получить целое число из расчетов.

Затем мы curr_bandwidth текущую скорость (или curr_bandwidth ) в массив bands , мы устанавливаем last_bytes для текущего bytesLoaded и отправляем новое событие типа BandwidthTester.TEST . Эта функция будет вызываться каждую секунду в течение трех секунд. В конце этих трех секунд у нас есть 3-элементный массив, который содержит значения скорости


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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private function timer_complete( e:TimerEvent ){
    l.close();
    bands.sort( Array.NUMERIC | Array.DESCENDING );
     
    peak_bandwidth = bands[0];
    bandwidth = calc_avg_bandwidth();
             
    dispatchEvent( new Event( BandwidthTester.BAND_TESTED ) );
}
 
private function end_download( e ){
    tm.stop();
    l.close();
    bands.sort( Array.NUMERIC | Array.DESCENDING );
    bandwidth = 10000;
    peak_bandwidth = (bands[0])?
             
    dispatchEvent( new Event( BandwidthTester.BAND_TESTED ) );
}

Функция timer_complete() вызывается после того, как таймер завершает три события таймера. Здесь мы прекращаем загрузку URLLoader, вызывая close() , и сортируем массив bands . Функция sort() принимает некоторые значения для поведения сортировки. Я объединил Array.NUMERIC , который сортирует на основе чисел, и Array.DESCENDING который устанавливает сортировку по максимальным и минимальным числам. Это отсортирует массив bands так, чтобы в bands[0] мы имели самую высокую измеренную скорость, которую мы используем для установки peak_bandwidth . Я также установил пропускную способность для результата calc_avg_bandwidth() , который мы вскоре calc_avg_bandwidth() . Я также отправляю событие BAND_TESTED .

Функция end_download() почти такая же, за исключением того, что, если массив band не был заполнен, я установил peak_bandwidth и bandwidth на 10000. Это будет происходить очень редко — только если у вас очень быстрое соединение, и файл загружается менее чем за секунду — в этом случае пропускная способность не является проблемой.


Давайте вычислим окончательную пропускную способность, создав среднее из 3 значений в массивеband []:

1
2
3
4
5
6
7
8
private function calc_avg_bandwidth(){
    var total = 0;
    var len = bands.length;
    while( len— ){
        total += bands[len];
    }
    return Math.round( total / bands.length );
}

Эта функция является типичной функцией усреднения: я добавляю все значения в массиве bands[] и делю результат на количество элементов в массиве, а затем возвращаю его с округлением до целого числа с помощью Math.round() . Эта функция усреднения гарантирует, что при наличии неравномерности между скоростями мы получим лучшее приближение ширины полосы.


Нам нужно создать еще несколько служебных функций:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public function set latency( prc ){
    this._latency = 1-prc;
}
         
public function getBandwidth(){
    return bandwidth;
}
         
public function getPeak(){
    return peak_bandwidth;
}
public function last_speed(){
    return curr_bandwidth;
}

Я определил функцию установки для свойства _latency , на случай, если вам нужно установить его после создания экземпляра. Это не обязательно, но это круто :). Я также определил функцию getBandwidth() которую мы будем использовать для получения окончательной полосы пропускания, и функцию getPeak() которая возвращает peak_bandwidth ; опять же, я мог бы сделать пропускную способность и peak_bandwidth публичными, но это более стандартно Функция last_speed() возвращает переменную curr_bandwidth , поэтому она будет содержать последнюю измеренную скорость.

Теперь getBandwidth() и getPeak() должны вызываться только после того, как BAND_TESTED событие BAND_TESTED , но BAND_TESTED мы увидим, как использовать этот класс. Вот окончательный код для этого класса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package {
    import flash.net.*;
    import flash.utils.*;
    import flash.events.*;
     
    public class BandwidthTester extends EventDispatcher {
        public static const BAND_TESTED = ‘tested’;
        public static const TEST = ‘test’;
         
        private var bandwidth = 0;
        private var peak_bandwidth = 0;
        private var curr_bandwidth = 0;
         
        private var testfile = ‘videos/video_hq.flv’;
        private var l;
        private var tm;
        private var last_bytes = 0;
        private var bands;
        private var _latency = 1;
         
         
        public function BandwidthTester( latency=0 ){
            tm = new Timer(1000, 3);
            tm.addEventListener( TimerEvent.TIMER, get_band );
            tm.addEventListener( TimerEvent.TIMER_COMPLETE, timer_complete );
            bands = new Array();
            _latency = 1-latency;
        }
        public function start(){
            l = new URLLoader();
            l.addEventListener( Event.OPEN, start_timer );
            l.addEventListener( Event.COMPLETE, end_download );
            l.load( new URLRequest( testfile ) );
        }
         
        public function start_timer( e:Event ){
            tm.start();
        }
         
        private function get_band( e:TimerEvent ){
            curr_bandwidth = Math.floor(((l.bytesLoaded-last_bytes) /125) * _latency);
            bands.push( curr_bandwidth );
            last_bytes = l.bytesLoaded;
            dispatchEvent( new Event( BandwidthTester.TEST ) );
        }
         
        private function timer_complete( e:TimerEvent ){
            l.close();
            bands.sort( Array.NUMERIC | Array.DESCENDING );
            peak_bandwidth = bands[0];
            bandwidth = calc_avg_bandwidth();
            dispatchEvent( new Event( BandwidthTester.BAND_TESTED ) );
        }
         
        private function end_download( e ){
            tm.stop();
            l.close();
            bands.sort( Array.NUMERIC | Array.DESCENDING );
            bandwidth = 10000;
            peak_bandwidth = (bands[0])?
            dispatchEvent( new Event( BandwidthTester.BAND_TESTED ) );
        }
         
        private function calc_avg_bandwidth(){
            var total = 0;
            var len = bands.length;
            while( len— ){
                total += bands[len];
            }
            return Math.round( total / bands.length );
        }
         
        public function set latency( prc ){
            this._latency = 1-prc;
        }
         
        public function getBandwidth(){
            return bandwidth;
        }
         
        public function getPeak(){
            return peak_bandwidth;
        }
         
    }
}

Ну, это было много кода, и мы еще не тестировали весь этот класс тестирования! Мы будем использовать этот класс в небольшом проигрывателе, который будет загружать три разных FLV в зависимости от скорости. Во Flash создайте новый документ Flash с размерами 640×480 пикселей и установите частоту кадров 30 кадров в секунду.

Плеер с пропускной способностью

Я установил черный цвет фона фильма (# 000000). Давайте создадим класс документа . Создайте новый файл Actionscript и назовите его BandwidthPlayerTest.as и введите следующее:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package {
     
    import flash.display.*;
    import flash.events.*;
    import flash.media.*;
    import flash.xml.*;
    import flash.text.*;
    import flash.net.*;
     
    public class BandwidthPlayerTest extends Sprite {
         
        var bt;
        var finalvideo = »;
        var video_id = false;
        var videos = new Array(‘videos/video_lq.flv’, ‘videos/video_mq.flv’, ‘videos/video_hq.flv’);
        var nc //net connection
        var stream //netstream
        var t;
         
        public function BandwidthPlayerTest(){
            t = new TextField();
            t.defaultTextFormat = new TextFormat(‘Arial’,12,0xffffff);
            t.width = 200;
            t.autoSize = TextFieldAutoSize.LEFT;
            tx = 10;
            ty = 10;
            t.selectable = false;
            t.text = ‘Calculating speed …’;
            addChild( t );
             
            this.bt = new BandwidthTester(0);
            this.bt.addEventListener( BandwidthTester.BAND_TESTED, play_video );
            this.bt.addEventListener( BandwidthTester.TEST, band_test );
            this.bt.start();
        }
         
         
         
    }
}

Это основной класс документа для Flash-документа. Я импортировал пакеты flash.media и flash.net , так как мы будем использовать класс Video для воспроизведения видео. Давайте рассмотрим переменные, которые я установил для этого класса:

bt будет содержать экземпляр тестера пропускной способности, finalvideo будет содержать выбранный путь к видео, video_id будет содержать выбранный индекс (это понадобится нам позже), nc и stream будут содержать классы NetConnection и NetStream, а videos — это массив, который держит пути к видео.

В конструкторе мы создаем новое текстовое поле (для тестирования) и создаем экземпляр класса BandwidthTester. Я указал значение 0 для задержки, поскольку нам это сейчас не нужно.


Давайте создадим функции band_test() и play_video() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private function band_test( e ){
    t.text += ‘\ntest: ‘+e.target.last_speed()+’ kb/s’;
}
     
private function play_video( e ){
    var bw = e.target.getBandwidth();
         
    t.text += ‘\n\nFinal bandwidth: ‘+bw+’ kb/s’;
    t.text += ‘\nPeak bandwidth: ‘+e.target.getPeak()+’ kb/s’;
 
    if( bw > 400 ){
        video_id = 2;
    } else if( bw > 128 ){
        video_id = 1;
    } else {
        video_id = 0;
    }
    finalvideo = videos[video_id];
         
    nc = new NetConnection();
    nc.addEventListener( NetStatusEvent.NET_STATUS, nc_status );
    nc.connect( null );
}

Вы можете видеть, что я добавил прослушиватель BandwidthTester.TEST который будет запускать band_test() каждую секунду, поэтому мы получаем измеренную скорость и добавляем ее в текстовое поле. Очевидно, что в живом приложении это будет скрыто, и вы, вероятно, будете использовать это только для отладки.

Следующая функция play_video() — это место, где мы выбираем правильный FLV. Я получаю среднюю пропускную способность в переменной bw , добавляю некоторые результаты в текстовое поле и начинаю сравнивать скорости. Если мы получаем скорость выше 400 кбит / с, мы играем самую высокую версию, в противном случае мы играем более низкие версии. Переменная video_id хранит индекс в массиве videos[] .


Давайте создадим вспомогательные функции для загрузки FLV:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private function nc_status( e ){
    switch( e.info.code ){
        case «NetConnection.Connect.Success»:
            connect_stream();
            break;
        case «NetStream.Play.StreamNotFound»:
            t.text = ‘Could not find video.’;
            break;
    }
}
         
private function connect_stream(){
    stream = new NetStream( nc );
    stream.addEventListener( NetStatusEvent.NET_STATUS, nc_status );
    stream.addEventListener( AsyncErrorEvent.ASYNC_ERROR, function(e){ } );
             
    var video = new Video( stage.stageWidth, stage.stageHeight );
    addChildAt( video, 0 );
    video.attachNetStream( stream );
    video.smoothing = true;
    stream.play( finalvideo );
    t.text += ‘\nVideo ‘+finalvideo;
}

nc_status() довольно стандартна в загрузке FLV, мы вызываем функцию connect_stream() если мы получаем NetConnection.Connect.Success , или же мы показываем ошибку в текстовом поле (если фильм не может быть загружен).

Функция connect_stream() — это место, где мы присоединяем сетевое соединение к NetStream. Я добавил прослушиватель событий для AsyncErrorEvent.ASYNC_ERROR к которому я прикрепил пустую функцию, чтобы остановить отслеживание асинхронных ошибок в окне трассировки, так как это на самом деле не нужно.

Итак, я определил видеообъект, прикрепил поток и вызвал play() . Довольно просто, а? Я также установил smoothing в true чтобы FLV выглядела лучше.

Вернувшись в основной документ Flash, установите для класса документа значение BandwidthPlayerTest . Если вы сейчас тестируете фильм, вы должны получить три секунды времени измерения, после чего загрузится FLV. На данный момент мы не можем точно смоделировать все скорости, поэтому вы можете экспортировать SWF-файл и поместить его на сервер (вместе с видео, конечно) и протестировать скорость. Вы должны получить правильный фильм в зависимости от скорости загрузки.


В качестве дополнения я покажу вам, как создать качественный селектор для плеера. В документе Flash выберите инструмент «Прямоугольник» (R), установите круглость прямоугольника на 100 и нарисуйте прямоугольник размером 90×25 пикселей с заливкой # 333333, как показано на рисунке:

Плеер с пропускной способностью

Нажмите F8, чтобы создать новый мувиклип из скругленного прямоугольника. Назовите этот клип video_selector и нажмите «Дополнительно». Проверьте Экспорт для Actionscript и в поле Идентификатор введите video_sel .

Плеер с пропускной способностью

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

Плеер с пропускной способностью

На слое кнопок создайте прямоугольник размером 30×25 пикселей с цветом заливки # CC0000. Выделив прямоугольник, нажмите F8 и создайте новую кнопку с button имени, как на рисунке:

Плеер с пропускной способностью

Дважды щелкните по вновь созданной кнопке и переместите первый кадр в кадр «Над»; затем дублируйте рамку (удерживая Alt и перетаскивая рамку) в состояния Down и Hit. Если вы хотите, вы можете использовать более темный оттенок красного в состоянии Down, чтобы сделать его более интересным.

Плеер с пропускной способностью

Вернувшись в видеоклип «video_selector», дважды продублируйте кнопку, чтобы у вас было три кнопки, и расположите их на 0, 30 и 60 пикселей соответственно. Назовите три кнопки b1 , b2 и b3 .

Плеер с пропускной способностью

Создайте еще один прямоугольник размером 30×25 пикселей с цветом заливки # CC0000, нажмите F8 и превратите его в видеоклип с именем hover . Вернитесь к video_selector , добавьте экземпляр этого мувиклипа, video_selector ему имя экземпляра hover и video_selector в video_selector .


Выберите прямоугольник со скругленными углами в слое bg и нажмите Copy (Ctrl + C) и Paste (Ctrl + V) на слое маски и установите слой маски как маску, как на картинке. Установите кнопки и слой наведения как маскированные слои. Если вы протестируете фильм сейчас, вы получите красивую панель с тремя кнопками. Давайте создадим ярлык для кнопок.

Плеер с пропускной способностью

На текстовом слое создайте новое статическое текстовое поле и введите LQ , MQ и HQ . Оставьте несколько пробелов для выравнивания каждой надписи над соответствующей кнопкой или создайте три отдельных текстовых поля; в любом случае, это должно выглядеть так:

Плеер с пропускной способностью

Давайте создадим функциональность. Мы хотим иметь возможность нажимать на качественную кнопку и загружать определенный фильм. Добавьте этот код в BandwidthPlayerTest.as после функции connect_stream() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
private function load_video_lq( e ){
    t.text = this.videos[0];
    stream.play( this.videos[0] );
    video_selector.hover.x = 0;
}
         
private function load_video_mq( e ){
    t.text = this.videos[1];
    stream.play( this.videos[1] );
    video_selector.hover.x = 30;
}
         
private function load_video_hq( e ){
    t.text = this.videos[2];
    stream.play( this.videos[2] );
    video_selector.hover.x = 60;
}

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


В конструкторе добавьте эту строку:

1
video_selector.alpha = 0;

Добавьте эту строку в play_video() :

1
create_video_selector(video_id);

И, наконец, давайте создадим create_video_selector() :

1
2
3
4
5
6
7
private function create_video_selector( hover ){
    video_selector.hover.x = hover*30;
    video_selector.alpha = 1;
    video_selector.b1.addEventListener( MouseEvent.CLICK, load_video_lq );
    video_selector.b2.addEventListener( MouseEvent.CLICK, load_video_mq );
    video_selector.b3.addEventListener( MouseEvent.CLICK, load_video_hq );
}

Мы передаем video_id этой функции; он содержит 0 , 1 или 2 . Затем мы устанавливаем прямоугольник при наведении на соответствующую позицию, используя hover*30 . Наконец, мы показываем video_selector и добавляем три прослушивателя событий к кнопкам b1, b2 и b3.

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


Это конец урока, надеюсь, вам понравилось! Оставьте комментарий, если вы создали улучшенную функциональность для игрока, мне будет интересно услышать, что вы сделали!