-Поиск по дневнику

Поиск сообщений в rss_rss_hh_new

 -Подписка по e-mail

 

 -Статистика

Статистика LiveInternet.ru: показано количество хитов и посетителей
Создан: 17.03.2011
Записей:
Комментариев:
Написано: 51


Аудиоплеер с плейлистом на javascript

Среда, 13 Сентября 2017 г. 14:43 + в цитатник
greebn9k сегодня в 14:43 Разработка

Аудиоплеер с плейлистом на javascript

    Здравствуйте, уважаемые читатели. Сегодня хотелось бы рассказать Вам о том, как сделать простейший кастомный аудиоплеер с плейлистом. Будем делать его, используя HTML5 и Javascript. В результате, получим вот такой простенький плеер. По внешнему виду похожий на плеер vk.com)

    Пример
    Самый простой способ для воспроизведения аудиофайлов в браузере — использование тэга
    :
    Например:
     
     

    Атрибут
    controls
    отображает стандартные элементы управления, такие как кнопка play/pause, mute.

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

    Чтобы воспроизвести аудио, используя Javascript, достаточно лишь две строки кода:
    let file = new Audio('track1.mp3');
    file.play(); 

    Тут создается объект Audio, им-то мы и будем оперировать. Вот краткий список общих методов и свойств:
    .play()
    запустить проигрывание аудиофайла;
    .pause()
    поставить на паузу;
    .duration 
    длина дорожки (в секундах). Duration становится доступен только после срабатывания события ‘loadedmetadata’;
     currentTime
    получить/установить момент проигрывания звуковой дорожки;

    Полный список доступен тут.

    Так как данная реализация всего-лишь фронтенд пример, то добавим аудио файлы в локальную папку files (для удобства и налгядности) и захардкодим их названия:
    const FILES = [
     'track1',
     'track2',
     'track3',
     'track4' 
    ]

    В продакшене нельзя использовать такой подход по очевидным причинам.
    Теперь создадим класс Player с такими методами:
    init — инициализация плейлиста;
    loadFile — загрузить дорожку, при запросе на запуск, только если она еще не было загружена.
    play — запустить/поставить на паузу дорожку;
    playNext — запустить следующую дорожку;
    playPrev — запустить предыдущую дорожку;
    setTitle — установить название файла в шапке плеера;
    setProgress — установить процент на сколько заполнен прогресс-бар;
    countProgress — посчитать в процентах количество проигранного времени;
    runProgress — запустить отрисовку заполнения прогресс-бара;
    stopProgress — сбросить процент заполненности прогресс-бара;
    pickNewProgress — навигация по файлу с помощью клика по прогресс-бару;
    toggleStyles — метод для изменения стилей кнопки play/pause и плейлиста;

    Код плеера:
     class Player {
     constructor(files) {
       this.current = null;
       this.status = 'pause';
       this.progress = 0;
       this.progressTimeout = null;
       this.files = FILES.map(name => {
         return {
           name: name
         }
       });
     }
     init() {
       let playlist = getByQuery('.playlist');
     
       this.files.forEach((f, i) => {
         let playlistFileContainer = createElem({
           type: 'div',
           appendTo: playlist,
           textContent: f.name,
           class: 'fileEntity',
           handlers: {
             click: this.play.bind(this, null, i)
           }
         });
         createElem({
           type: 'div',
           appendTo: playlistFileContainer,
           textContent: '--:--',
           class: 'fileEntity_duration',
         })
       });
     }
     loadFile(i) {
     
       let f = this.files[i];
       f.file = new Audio(prepareFilePath(f.name));
     
       f.file.addEventListener('loadedmetadata', () => {
         getByQuery('.playlist').children[i].children[0].textContent = prettifyTime(f.file.duration);
       });
     
       f.file.addEventListener('ended', this.playNext.bind(this, null, i));
     }
     
     play(e, i = this.current || 0) {
       if (!this.files[i].file) {
         this.loadFile(i);
       }
     
       let action = 'play';
     
       if (this.current == i) {
         action = this.status === 'pause' ? 'play' : 'pause';
         this.toggleStyles(action, i);
       } else if (typeof this.current !== 'object') {
         this.files[this.current].file.pause();
         this.files[this.current].file.currentTime = 0;
         this.toggleStyles(action, this.current, i);
       } else {
         this.toggleStyles(action, i);
       }
     
       this.current = i;
       this.status = action;
       this.files[i].file[action]();
     
       if (action == 'play') {
         this.setTitle(this.files[i].name);
         this.stopProgress();
         this.runProgress();
       } else {
         this.stopProgress();
       }
     }
     
     playNext(e, currentIndex) {
       let nextIndex = (currentIndex ? currentIndex : this.current) + 1;
     
       if (!this.files[nextIndex]) {
         nextIndex = 0;
       }
     
       this.play(null, nextIndex);
     }
     
     playPrev(e, currentIndex) {
       let prevIndex = (currentIndex ? currentIndex : this.current) - 1;
     
       if (!this.files[prevIndex]) {
         prevIndex = this.files.length - 1;
       }
     
       this.play(null, prevIndex);
     }
     
     setTitle(title) {
       getByQuery('.progress_bar_title').textContent = title;
     }
     
     setProgress(percent = 0, cb) {
       getByQuery('.progress_bar_container_percentage').style.width = `${percent}%`;
       cb && cb();
     }
     
     countProgress() {
       let file = this.files[this.current].file;
     
       return (file.currentTime * 100 / file.duration) || 0;
     }
     
     runProgress(percent = 0) {
       let percentage = percent || this.countProgress();
       let cb = percent ? () => {
         this.files[this.current].file.currentTime = percentage * this.files[this.current].file.duration / 100;
       } : null;
     
       this.setProgress(percentage, cb);
       this.progressTimeout = setTimeout(this.runProgress.bind(this), 1000)
     }
     
     stopProgress() {
       clearTimeout(this.progressTimeout);
       this.progressTimeout = null;
     }
     
     pickNewProgress(e) {
       if (this.status != 'play') {
         this.play();
       }
     
       let coords = e.target.getBoundingClientRect().left;
       let progressBar = getByQuery('.progress_bar_stripe');
       let newPercent = (e.clientX - coords) / progressBar.offsetWidth * 100;
     
       this.stopProgress();
       this.runProgress(newPercent);
     }
     
     toggleStyles(action, prev, next) {
       let prevNode = getByQuery('.playlist').children[prev];
       let nextNode = getByQuery('.playlist').children[next];
       let playPause = getByQuery('.play_pause .play_pause_icon');
     
       if (!next && next !== 0) {
         if (!prevNode.classList.contains('fileEntity-active')) {
           prevNode.classList.add('fileEntity-active');
         }
         playPause.classList.toggle('play_pause-play');
         playPause.classList.toggle('play_pause-pause');
       } else {
         prevNode.classList.toggle('fileEntity-active');
         nextNode.classList.toggle('fileEntity-active');
       }
     
       if (playPause.classList.contains('play_pause-play') && action == 'play' && prev != next) {
         playPause.classList.toggle('play_pause-play');
         playPause.classList.toggle('play_pause-pause');
       }
     }
    }
    



    Далее цепляемся на событие
    DOMContentLoaded
    инициализируем наш плеер и вешаем обработчики на элементы управления:

    window.addEventListener('DOMContentLoaded', initHandlers);
     
    function initHandlers() {
     let player = new Player(FILES);
     player.init();
     
     getByQuery('.player .controls .play_pause').addEventListener('click', player.play.bind(player));
     getByQuery('.player .controls .navigation_prev').addEventListener('click', player.playPrev.bind(player));
     getByQuery('.player .controls .navigation_next').addEventListener('click', player.playNext.bind(player));
     getByQuery('.player .controls .progress_bar_stripe').addEventListener('click', player.pickNewProgress.bind(player));
    }
    

    Функция
    getByQuery()
    сокращенный вариант
    document.querySelector()
    :
    А вот она в коде:
    function getByQuery(elem) {
     return typeof elem === 'string' ? document.querySelector(elem) : elem;
    }

    Помимо этого было использовано еще несколько кастомных функций, код которых можно найти в репозитории.
    Логика работы плеера очень простая. Мы читаем массив файлов и записываем его в
    this
    . Кликнув по дорожке, в плейлисте вызывается метод
    play
    . Ему мы передаем индекс файла в массиве. Он подгружает дорожку, используя метод
    loadFile
    Только если дорожка еще не была загружена. Затем метод запускает ее проигрывание. Не забываем и за прогресс-бар. В контексте плеера мы также храним статус (play/pause), индекс текущего файла и переменную с таймаутом. Таймаут мы используем, чтоб раз в секунду (при условии проигрывания файла) мы могли перерисовывать прогресс-бар. Ссылка на демо.

    В следующей статье разберем более сложные способы реализации аудиоплеера.
    Спасибо за внимание.

    Над статьей работали varog-norman и greebn9k
    Original source: habrahabr.ru (comments, light).

    https://habrahabr.ru/post/337846/

    Метки:  

     

    Добавить комментарий:
    Текст комментария: смайлики

    Проверка орфографии: (найти ошибки)

    Прикрепить картинку:

     Переводить URL в ссылку
     Подписаться на комментарии
     Подписать картинку