Slyweb
На разработку сайта! Скидки 50%!

Аудио плеер на основе HTML5 и jQuery

jQuery Nivo Slider

Сегодня я расскажу как сделать интересный вариант плеера на основе html 5 и jQuery.





Плеер будет поддерживать следующие функции:

  • перемотка
  • старт/стоп
  • пауза
  • индикатор оставшегося времени
  • индикатор загрузки файла

В итоге Вы научитесь не только делать кросбраузерный плеер, но простой плагин на jQuery, так как плеер мы будем подключать как плагин jQuery. В чём основное отличие плагина jQuery от простого javascript кода реализующегося те же функции - плеер может создаваться неоднократно на странице. То есть написав:

  • Код
  • Чистый код
  • Копировать в буфер
  1.$('.m').audioplayer('init',{file:'audio/250_m',t:'m'});
  2.$('.w').audioplayer('init',{file:'audio/250_w',t:'w'});

        

плагин правильно подключится дважды, как на изображении. В плагин jQuery мы передаём два параметра первый - наименование функции (init) содержащейся в плаигне, второй объект, содержащий опции плагина ({file:'250_m',t:'m'}). Где file имя файла, t - тип плеера, он может быть розовым (w) или синим (m). Сразу скажу, все графические элементы плеера свободны для использования и распространения, да и плеер распространяется на лицензии GNU GPL 2. То есть у Вас есть всеобъёмлющий набор прав по распоряжению исходными материалами плеера.

Живой пример

Для его работы ипользуется следующя hrml разметка:

  • Код
  • Чистый код
  • Копировать в буфер
  1.<form action="index.php" method="POST">
  2.<div class="conteinerofplayer">
  3.
  4.    
  5.
  6.
  7.    <div class="player m">
  8.        <div class="playbut_m">
  9.    
10.        </div>
11.        <div class="bar">
12.         <div class="progress_m">
13.                <span></span>
14.                <div class="line_m">
15.    
16.                </div>
17.            </div>
18.            <div class="tracktime">
19.                <span></span>
20.            </div>
21.        </div>
22.        <div class="radionoactive_m radio_m">
23.    
24.        </div>
25.    
26.    </div>
27.    
28. <div class="player w">
29.        <div class="playbut_w">
30.    
31.        </div>
32.        <div class="bar">
33.         <div class="progress_w">
34.                <span></span>
35.                <div class="line_w">
36.    
37.                </div>
38.            </div>
39.            <div class="tracktime">
40.                <span></span>
41.            </div>
42.        </div>
43.        <div class="radionoactive_w radio_w">
44.    
45.        </div>
46.    
47.    </div>
48.
49.    <!--
50.     <div class="player m2">
51.        <div class="playbut_m">
52.    
53.        </div>
54.        <div class="bar">
55.         <div class="progress_m">
56.                <span></span>
57.                <div class="line_m">
58.    
59.                </div>
60.            </div>
61.            <div class="tracktime">
62.                <span></span>
63.            </div>
64.        </div>
65.        <div class="radionoactive_m radio_m">
66.    
67.        </div>
68.    
69.    </div>
70.    -->
71.</div>
72.<input type="submit" value="submit">
73.</form>

        

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

Для начала не забудьте подключить jQuery! Затем пишите плагин внутри обработчика события загрузки страницы:

  • Код
  • Чистый код
  • Копировать в буфер
  1.;(function($) {
  2.    .......
  3.})(jQuery);

        

Любой плагин jQuery расширяет его, для это есть стандартная функция jQuery $.fn.extend:

  • Код
  • Чистый код
  • Копировать в буфер
  1.$.fn.extend({
  2.    
  3.    
  4.});
  5.

        

Содержимое $.fn.extend для нашего плагина будет следующим:

  • Код
  • Чистый код
  • Копировать в буфер
  1.        audioplayer: function(arg,options) {
  2.
  3.         if (options && typeof(options) == 'object') {
  4.
  5.                options = $.extend( {}, $.audioplayer.defaults, options );
  6.            } else {
  7.                options = $.audioplayer.defaults;
  8.            }
  9.            
10.
11.            // this creates a plugin for each element in
12.            // the selector or runs the function once per
13.            // selector. To have it do so for just the
14.            // first element (once), return false after
15.            // creating the plugin to stop the each iteration
16.            this.each(function() {
17.    
18.             new $.audioplayer(this, options, arg );
19.            });
20.         return;
21.        }
22.

        

В общем здесь нет ничего сложного, мы добавляем опции плагина и запускаем для каждого объекта this отдельный плагин, название плагина я выбрал "audioplayer". Основа - готова!

Далее код плагина. Сперва я приведу его целиком, затем объясню отдельные части этого кода.

  • Код
  • Чистый код
  • Копировать в буфер
  1.    $.audioplayer = function( elem,options,arg) {
  2.
  3.     if (arg && typeof(arg) == 'string') {
  4.         if (arg == 'init') {
  5.             init( arg );
  6.         }
  7.         else if (arg == 'remove') {
  8.             remove( arg );
  9.         }
10.         return;
11.        }
12.
13.        /*...normal plugin actions...*/
14.
15.        function init(arg)
16.        {
17.            var isload = {};
18.            var container = $(elem);
19.            
20.            var isIE = !!document.all && document.uniqueID;
21.            var isChrome = false;
22.            var btfound = 0; browser_detect = navigator.userAgent.toLowerCase();
23.            if(browser_detect.indexOf("chrome") + 1) isChrome = true;
24.            
25.            if(options.t == 'w') {
26.            
27.                var tclass = 'qtip-light';
28.                var _sclass = '_w';
29.            } else {
30.                
31.                var tclass = 'qtip-light-men';
32.                var _sclass = '_m';
33.            }
34.            i = container.index('.player');
35.
36.
37.            if(isIE || isChrome) {
38.                ext = 'mp3';
39.            } else {
40.                ext = 'ogg';    
41.            }
42.            container.append('<audio preload="metadata" class="track" src="'+options.file+'.'+ext+'"><p>Your browser does not support the audio element</p></audio>');
43.            $('.player:eq(0)').find('audio').addClass('aa');
44.
45.            if(i == 0) {
46.        
47.                acteviHidden(_sclass);
48.            }
49.            
50.    
51.            
52.            var audio = $(elem).find('audio')[0];
53.            
54.
55.                    $('.track',container).bind('progress', function(e) {
56.                         var ranges = [];
57.                         for(var i = 0; i < audio.buffered.length; i ++)
58.                         {
59.                            ranges.push([
60.                             audio.buffered.start(i),
61.                             audio.buffered.end(i)
62.                             ]);
63.                         }
64.                         d = audio.duration / 100;
65.
66.                        
67.                        //$('.bar .tracktime span',container).hide();
68.                        //now iterate through the ranges and convert each set of timings
69.                        //to a percentage position and width for the corresponding span
70.                        allbufer = 0;
71.                        for(var i = 0; i < audio.buffered.length; i ++)
72.                        {
73.                            allbufer += ranges[i][1]-ranges[i][0];
74.                            
75.                        }
76.                         //console.log(allbufer);
77.                                                 
78.                         per = Math.floor(allbufer / d);
79.                         if(per > 0) {
80.                             per = (per > 100) ? per ==100 : per;
81.                                $('.bar .progress'+_sclass+' span',container).text(per+" %");            
82.                         }
83.                    });
84.                    $('.track',container).bind('playing', function(e) {
85.                        
86.                        
87.                        $('.bar .tracktime span',container).show();
88.
89.
90.                    });    
91.
92.                // Is the player currently seeking?
93.                $('.track',container).bind('timeupdate', function() {
94.                    if(isload.clickoffset)
95.                    {
96.                        $('.progress'+_sclass,container).trigger('click');
97.                        isload.clickoffset = false;
98.                        return;
99.                    }
100.
101.                    $('.tracktime',container).show();
102.                    var time = this.duration - this.currentTime;
103.                    
104.                    var m = Math.floor(time / 60);
105.                    var s = Math.floor(time - (m * 60));
106.                    if(s < 10) s = '0'+s;
107.                    if(m < 10) m = '0'+m;
108.                    $('.tracktime span',container).text( m +':'+ s);
109.                    var step = 250 / this.duration;
110.                    $('.bar .progress'+_sclass+' .line'+_sclass,container).width((Math.floor(this.currentTime) * step)+'px');
111.        
112.                    if(Math.floor(this.duration) == Math.floor(this.currentTime)) {
113.                        
114.                        $('.playbut'+_sclass,container).removeClass('stop'+_sclass).addClass('play'+_sclass);
115.                        $('.progress'+_sclass+' .line'+_sclass,container).width(0);
116.                        $('.progress'+_sclass+' .tracktime',container).hide();
117.                    }
118.                });
119.                
120.                $('.playbut'+_sclass,container).bind('click', function() {
121.                    acteviHidden(_sclass);
122.                    $('audio').removeClass('aa');
123.                    $('audio',container).addClass('aa');
124.
125.
126.                    if($(this).hasClass('stop'+_sclass)) {
127.                        
128.                        $(this).removeClass('stop'+_sclass).addClass('play'+_sclass);
129.                        $('audio',container)[0].pause(); // jumps to 29th secs    
130.
131.                    } else {
132.
133.
134.                        $(this).removeClass('play'+_sclass).addClass('stop'+_sclass);
135.
136.                    
137.                            $('audio:not(".aa")').each(function(index, element) {
138.                                element.pause();
139.                                if(element.currentTime) {
140.                                    element.currentTime = 0;
141.                                }
142.                                $(this).siblings('.playbut_w').removeClass('stop_w').addClass('play_w');
143.                                $(this).siblings('.playbut_m').removeClass('stop_m').addClass('play_m');
144.                            });
145.                            $('.radio'+_sclass,container).addClass('radioactive'+_sclass);
146.                            
147.                            $('.radio_m').removeClass('radioactive_m');
148.                            $('.radio_w').removeClass('radioactive_w');
149.                            $('.radio_m').addClass('radionoactive_m');
150.                            $('.radio_w').addClass('radionoactive_w');
151.
152.                        $('.radio'+_sclass,container).removeClass('radionoactive'+_sclass).addClass('radioactive'+_sclass);
153.
154.                            $('.bar .tracktime span').hide();
155.
156.                            $('audio',container)[0].play(); // jumps to 29th secs
157.                            $('.tracktime',container).removeClass('loadm');
158.                    
159.                        
160.                        
161.                    }
162.            
163.                });
164.                
165.                $('.progress'+_sclass,container).bind('click', function(event) {
166.                
167.                    duration = audio.duration;
168.                    //get the position of the event
169.                    
170.                    clientX = event.clientX;
171.                    left = event.currentTarget.offsetLeft;
172.                    clickoffset = clientX - left;
173.                    if(isload.clickoffset) clickoffset = isload.clickoffset;
174.                    percent = clickoffset/event.currentTarget.offsetWidth;
175.                    duration_seek = percent*duration;
176.                    if($('audio',container)[0].currentTime) {
177.                        $('audio',container)[0].currentTime = duration_seek;
178.                    } else {
179.                        isload.clickoffset = clickoffset;    
180.                        
181.                        $('.bar .progress'+_sclass+' .line'+_sclass,container).width(clickoffset+'px');
182.                    }
183.                    
184.    
185.                     //document.getElementById('track').currentTime = duration_seek; // jumps to 29th secs
186.                    
187.                    //document.getElementById('track').play(); // jumps to 29th secs
188.            
189.                });
190.                
191.                $('.radio'+_sclass,container).bind('click', function() {
192.                    r = $(this);
193.                    acteviHidden(_sclass);
194.                
195.                    $('audio').removeClass('aa');
196.                    $('audio',container).addClass('aa');
197.
198.                        $('audio:not(".aa")').closest('.player').find('.playbut_w').removeClass('stop_w').addClass('play_w');
199.                        $('audio:not(".aa")').closest('.player').find('.playbut_m').removeClass('stop_m').addClass('play_m');
200.                            
201.                        if(r.hasClass('radionoactive'+_sclass)) {
202.                            $('.radio_w,.radio_m').removeClass('radioactive_w');
203.                            $('.radio_w,.radio_m').removeClass('radioactive_m');
204.    
205.
206.                            $('.playbut'+_sclass,container).removeClass('play'+_sclass).addClass('stop'+_sclass);
207.
208.                            $('.radio_w').addClass('radionoactive_w');
209.                            $('.radio_m').addClass('radionoactive_m');
210.                            
211.                            
212.                            $('audio:not(".aa")').each(function(index, element) {
213.                        
214.                                element.pause();
215.                                element.currentTime = 0;    
216.                            });
217.                            
218.                    
219.                            
220.                            
221.                            $('audio',container)[0].play(); // jumps to 29th secs
222.                            r.removeClass('radionoactive'+_sclass).addClass('radioactive'+_sclass);
223.                            $('.bar .tracktime span').hide();
224.                            $('.tracktime').removeClass('loadm');
225.
226.                        }
227.                        
228.                    });
229.        }
230.        
231.        function acteviHidden(type)
232.        {
233.            type = type.substr(1,1);    
234.            $('.conteinerofplayer input[name="voicetype"]').remove();
235.            var hidden = $('<input>',{name:'voicetype',value:type,type:'hidden'});
236.            $('.conteinerofplayer').append(hidden);    
237.            
238.        }
239.
240.    };

        

Методы плагина, запускаются следующим кодом, их два (init, remove), фактически в плигне используется один (init):

  • Код
  • Чистый код
  • Копировать в буфер
  1.     if (arg && typeof(arg) == 'string') {
  2.         if (arg == 'init') {
  3.             init( arg );
  4.         }
  5.         else if (arg == 'remove') {
  6.             remove( arg );
  7.         }
  8.         return;
  9.        }

        

В методе init сперва нужно определить общие для плагна перменные, здесь важно знать, что браузеры не поддерживают один формат, некоторые поддерживают mp3 другие ogg, поэтому нужно два файла с разными расширениям, если Вам нужен кросбраузерный плеер:

  • Код
  • Чистый код
  • Копировать в буфер
  1.var isload = {};
  2.var container = $(elem);
  3.            
  4.var isIE = !!document.all && document.uniqueID;
  5.var isChrome = false;
  6.var btfound = 0; browser_detect = navigator.userAgent.toLowerCase();
  7.if(browser_detect.indexOf("chrome") + 1) isChrome = true;

        

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

  • Код
  • Чистый код
  • Копировать в буфер
  1.if(isIE || isChrome) {
  2.    ext = 'mp3';
  3.} else {
  4.    ext = 'ogg';    
  5.}

        

Я не буду объяснять каждую переменную, остановлюсь, на основном. Например, обязательно нужно подключить для каждого объекта плагина тег audio, так как без него аудио проигрываться не будет. То есть мы подключили внутрь div плеера тег audio.

  • Код
  • Чистый код
  • Копировать в буфер
  1.container.append('<audio preload="metadata" class="track" src="'+options.file+'.'+ext+'"><p>Your browser does not support the audio element</p></audio>');

        

Функция acteviHidden необходима для присоединения к форме скрытого элемента input, в котором будет содержаться значение типа плагина (m или w). Думаю эту функцию можно легко приспособить для создания других скрытых полей с нужной вам информацией, если Вам нужно использовать плееры как внутри тега form.
Особое внимание я хотел бы уделить процессу буферизации данных, так как он отличается от обычных способов отслеживания загрузки файла. Для каждого плеера буферизация начинается с события $('.track',container).bind('progress'..... внутри которого данные о буфере помещаются в массив, который затем используется при расчётах процента загрузки аудио файла, ниже приведён код этого процесса:

  • Код
  • Чистый код
  • Копировать в буфер
  1.                    $('.track',container).bind('progress', function(e) {
  2.                         var ranges = [];
  3.                         for(var i = 0; i < audio.buffered.length; i ++)
  4.                         {
  5.                            ranges.push([
  6.                             audio.buffered.start(i),
  7.                             audio.buffered.end(i)
  8.                             ]);
  9.                         }
10.                         d = audio.duration / 100;
11.
12.                        
13.                        //$('.bar .tracktime span',container).hide();
14.                        //now iterate through the ranges and convert each set of timings
15.                        //to a percentage position and width for the corresponding span
16.                        allbufer = 0;
17.                        for(var i = 0; i < audio.buffered.length; i ++)
18.                        {
19.                            allbufer += ranges[i][1]-ranges[i][0];
20.                            
21.                        }
22.                         //console.log(allbufer);
23.                                                 
24.                         per = Math.floor(allbufer / d);
25.                         if(per > 0) {
26.                             per = (per > 100) ? per ==100 : per;
27.                                $('.bar .progress'+_sclass+' span',container).text(per+" %");            
28.                         }
29.                    });

        

Остальной код плеера связан с различными типами bind функций $('.track',container).bind('click',....., по просту говря эти функции отслеживают события нажатия графических элементов плеера, - старт, стоп, перемотка, чекбокс. Разобраться с ними Вам придётся самостоятельно, так как в них нет ничего сложно и они схожи по логике выполнения.

Рабочий плеера в архиве.

Загрузить архив audio-without-qtip.zip

Загрузить архив audio-with-qtip.zip


Александр Ермаков