【问题标题】:Buffering in jPlayerjPlayer 中的缓冲
【发布时间】:2019-02-04 17:02:58
【问题描述】:

我们在我们的网站上使用 Icecast 流媒体服务器到 jPlayer,也用于我们的移动应用程序。我试图将<intro> 添加到 Icecast 配置中,但是当我这样做时,它会在移动设备上出现问题。每当电话中断导致暂时断开连接时,例如来电,流会重复您第一次连接到流时开始收听的内容,当然在再次播放介绍之后。例如,如果我开始直播收听一个节目或歌曲,一个通话进入并结束,介绍会在重新连接时播放,直播将从我最初开始收听的位置开始播放。

我玩过上下设置 Icecast 队列和突发设置,但根本没有,并尝试了不同的格式,结果相同。我还与其他一些流媒体相关的帖子进行了对话,并被告知似乎问题出在客户端缓冲区和播放器上,而我没有设置。我查看了我们的 stream-player.js,它是 jPlayer 2.9.2,在第 3507 行添加了以下内容:

;(function() {
  var DOMParser, find, parse;

  DOMParser = (typeof window !== "undefined" && window !== null ? window.DOMParser : void 0) || (typeof require === "function" ? require('xmldom').DOMParser : void 0) || function() {};

  find = function(node, list) {
    var attributes, childNode, childNodeName, childNodes, i, match, x, _i, _j, _ref, _ref1;
    if (node.hasChildNodes()) {
      childNodes = node.childNodes;
      for (i = _i = 0, _ref = childNodes.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
        childNode = childNodes[i];
        childNodeName = childNode.nodeName;
        if (/REF/i.test(childNodeName)) {
          attributes = childNode.attributes;
          for (x = _j = 0, _ref1 = attributes.length; 0 <= _ref1 ? _j < _ref1 : _j > _ref1; x = 0 <= _ref1 ? ++_j : --_j) {
            match = attributes[x].nodeName.match(/HREF/i);
            if (match) {
              list.push({
                file: childNode.getAttribute(match[0]).trim()
              });
              break;
            }
          }
        } else if (childNodeName !== '#text') {
          find(childNode, list);
        }
      }
    }
    return null;
  };

  parse = function(playlist) {
    var doc, ret;
    ret = [];
    doc = (new DOMParser()).parseFromString(playlist, 'text/xml').documentElement;
    if (!doc) {
      return ret;
    }
    find(doc, ret);
    return ret;
  };

  (typeof module !== "undefined" && module !== null ? module.exports : window).ASX = {
    name: 'asx',
    parse: parse
  };

}).call(this);

(function() {
  var COMMENT_RE, EXTENDED, comments, empty, extended, parse, simple;

  EXTENDED = '#EXTM3U';

  COMMENT_RE = /:(?:(-?\d+),(.+)\s*-\s*(.+)|(.+))\n(.+)/;

  extended = function(line) {
    var match;
    match = line.match(COMMENT_RE);
    if (match && match.length === 6) {
      return {
        length: match[1] || 0,
        artist: match[2] || '',
        title: match[4] || match[3],
        file: match[5].trim()
      };
    }
  };

  simple = function(string) {
    return {
      file: string.trim()
    };
  };

  empty = function(line) {
    return !!line.trim().length;
  };

  comments = function(line) {
    return line[0] !== '#';
  };

  parse = function(playlist) {
    var firstNewline;
    playlist = playlist.replace(/\r/g, '');
    firstNewline = playlist.search('\n');
    if (playlist.substr(0, firstNewline) === EXTENDED) {
      return playlist.substr(firstNewline).split('\n#').filter(empty).map(extended);
    } else {
      return playlist.split('\n').filter(empty).filter(comments).map(simple);
    }
  };

  (typeof module !== "undefined" && module !== null ? module.exports : window).M3U = {
    name: 'm3u',
    parse: parse
  };

}).call(this);

(function() {
  var LISTING_RE, parse;

  LISTING_RE = /(file|title|length)(\d+)=(.+)\r?/i;

  parse = function(playlist) {
    var index, key, line, match, tracks, value, _, _i, _len, _ref;
    tracks = [];
    _ref = playlist.trim().split('\n');
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      line = _ref[_i];
      match = line.match(LISTING_RE);
      if (match && match.length === 4) {
        _ = match[0], key = match[1], index = match[2], value = match[3];
        if (!tracks[index]) {
          tracks[index] = {};
        }
        tracks[index][key.toLowerCase()] = value;
      }
    }
    return tracks.filter(function(track) {
      return track != null;
    });
  };

  (typeof module !== "undefined" && module !== null ? module.exports : window).PLS = {
    name: 'pls',
    parse: parse
  };

}).call(this);
;(function() {
  var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

  window.PlayerUI = (function() {
    function PlayerUI(container) {
      var _this = this;
      this.container = container;
      this.onStateButtonClicked = __bind(this.onStateButtonClicked, this);
      this.duration = null;
      this.state = 'loading';
      this.player = $('<div></div>');
      this.container.append(this.player);
      this.player.jPlayer({
        ready: function() {
          return _this.state = 'paused';
        }
      });
      this.volume = this.container.find('.volume-slider input').rangeslider({
        polyfill: false,
        onSlide: function(position, value) {
          return _this.player.jPlayer('volume', value / 100.0);
        },
        onSlideEnd: function(position, value) {
          return _this.player.jPlayer('volume', value / 100.0);
        }
      });
      this.hookEvents();
    }

    PlayerUI.prototype.hookEvents = function() {
      var _this = this;
      this.container.find('.state-button a').click(this.onStateButtonClicked);
      this.player.on($.jPlayer.event.play, function() {
        return _this.setState('playing');
      });
      this.player.on($.jPlayer.event.pause, function() {
        return _this.setState('paused');
      });
      this.player.on($.jPlayer.event.durationchange, function(e) {
        return _this.container.trigger('player.setProgressMax', {
          maxValue: e.jPlayer.status.duration
        });
      });
      this.player.on($.jPlayer.event.timeupdate, function(e) {
        return _this.container.trigger('player.updateProgress', {
          value: e.jPlayer.status.currentTime
        });
      });
      return this.player.on($.jPlayer.event.ended, function(e) {
        return _this.container.trigger('player.trackEnded');
      });
    };

    PlayerUI.prototype.setState = function(state) {
      this.state = state;
      return this.container.find('.state-button a').removeClass().addClass("state-" + state);
    };

    PlayerUI.prototype.onStateButtonClicked = function(event) {
      event.preventDefault();
      switch (this.state) {
        case 'playing':
          return this.pause();
        case 'paused':
          return this.play();
        default:
          return this.noop();
      }
    };

    PlayerUI.prototype.setMedia = function(media) {
      this.pause();
      return this.player.jPlayer('setMedia', media);
    };

    PlayerUI.prototype.setProgress = function(pct) {
      return this.player.jPlayer('playHead', pct);
    };

    PlayerUI.prototype.play = function() {
      this.setState('playing');
      return this.player.jPlayer('play');
    };

    PlayerUI.prototype.pause = function() {
      this.setState('paused');
      return this.player.jPlayer('pause');
    };

    PlayerUI.prototype.noop = function() {
      return null;
    };

    return PlayerUI;

  })();

}).call(this);
;(function() {
  window.PlaylistUI = (function() {
    function PlaylistUI(container) {
      var _this = this;
      this.container = container;
      this.container.hide();
      $(window).on('playlistloader.finished', function(evt, data) {
        return _this.setPlaylist(PlaylistLoader.coalescePlaylists(data.playlists));
      });
    }

    PlaylistUI.prototype.loadM3UList = function(m3uList) {
      return new PlaylistLoader(m3uList);
    };

    PlaylistUI.prototype.setPlaylist = function(playlistData) {
      if (typeof playlistData.data !== 'undefined') {
        this.name = playlistData.name;
        playlistData = playlistData.data;
      }
      this.playlist = playlistData;
      this.container.hide();
      this.unhookEvents();
      this.renderPlaylist();
      this.container.show();
      this.hookEvents();
      return this.container.trigger('playlistui.ready', {
        ui: this,
        autoplay: false //this.getAutoplay()
      });
    };

    PlaylistUI.prototype.unhookEvents = function() {
      return this.container.find('.playlist-item').off('click.playlistUI', 'a');
    };

    PlaylistUI.prototype.hookEvents = function() {
      var _this = this;
      return this.container.find('.playlist-item').on('click.playlistUI', 'a', function(evt) {
        var idx, item;
        evt.preventDefault();
        idx = $(evt.target).parent('.playlist-item').data('idx');
        item = _this.getItemByIdx(idx);
        return _this.select(item);
      });
    };

    PlaylistUI.prototype.renderPlaylist = function() {
      var idx, item, playlist, _i, _len, _ref, _results;
      playlist = this.container.find('.playlist');
      playlist.empty();
      _ref = this.playlist;
      _results = [];
      for (idx = _i = 0, _len = _ref.length; _i < _len; idx = ++_i) {
        item = _ref[idx];
        _results.push(playlist.append(this.rowTemplate(item, idx)));
      }
      return _results;
    };

    PlaylistUI.prototype.rowTemplate = function(item, idx) {
      return $("<li class=\"playlist-item\" data-idx=\"" + idx + "\"><a href=\"" + item.file + "\">" + item.title + "</a></li>");
    };

    PlaylistUI.prototype.getAutoplay = function() {
      var item, _i, _len, _ref;
      _ref = this.playlist;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        item = _ref[_i];
        if (item.autoplay) {
          return item;
        }
      }
      return null;
    };

    PlaylistUI.prototype.getItemByIdx = function(idx) {
      return this.playlist[idx];
    };

    PlaylistUI.prototype.getRowForItem = function(item) {
      var compare, found, idx, _i, _len, _ref;
      _ref = this.playlist;
      for (idx = _i = 0, _len = _ref.length; _i < _len; idx = ++_i) {
        compare = _ref[idx];
        if (compare === item) {
          found = this.container.find(".playlist-item[data-idx=" + idx + "]");
          return found;
        }
      }
      return null;
    };

    PlaylistUI.prototype.getIndexForItem = function(item) {
      var compare, idx, _i, _len, _ref;
      _ref = this.playlist;
      for (idx = _i = 0, _len = _ref.length; _i < _len; idx = ++_i) {
        compare = _ref[idx];
        if (item === compare) {
          return idx;
        }
      }
      return null;
    };

    PlaylistUI.prototype.findNext = function() {
      var currentIndex, nextIndex;
      currentIndex = this.getIndexForItem(this.current);
      if (currentIndex === null) {
        return null;
      }
      nextIndex = currentIndex + 1;
      if (nextIndex >= this.playlist.length) {
        return null;
      }
      return this.playlist[nextIndex];
    };

    PlaylistUI.prototype.select = function(item) {
      if (item) {
          this.current = item;
          this.getRowForItem(item).addClass('selected').siblings().removeClass('selected');
          return this.container.trigger('playlistui.select', {
            ui: this,
            item: item
        });
      }
    };

    PlaylistUI.prototype.selectFirst = function() {
      return this.select(this.playlist[0]);
    };

    PlaylistUI.prototype.selectNext = function() {
      var nextItem;
      nextItem = this.findNext();
      if (nextItem === null) {
        return false;
      }
      this.select(nextItem);
      return true;
    };

    return PlaylistUI;

  })();

}).call(this);
;(function() {
  var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

  window.PlaylistLoader = (function() {
    function PlaylistLoader(playlists) {
      this.playlists = playlists;
      this.loadedItem = __bind(this.loadedItem, this);
      this.loadPlaylists();
    }

    PlaylistLoader.prototype.loadPlaylists = function() {
      var idx, item, _i, _len, _ref, _results,
        _this = this;
      this.loadCount = 0;
      this.data = new Array(this.playlists.length);
      _ref = this.playlists;
      _results = [];
      for (idx = _i = 0, _len = _ref.length; _i < _len; idx = ++_i) {
        item = _ref[idx];
        _results.push((function() {
          var tmp;
          tmp = idx;
          return jQuery.ajax({
            url: item
          }).done(function(data) {
            return _this.loadedItem(tmp, data);
          });
        })());
      }
      return _results;
    };

    PlaylistLoader.prototype.loadedItem = function(idx, data) {
      var playlist;
      playlist = M3U.parse(data);
      this.data[idx] = playlist;
      $(window).trigger('playlistloader.loadeditem', {
        index: idx,
        playlist: playlist
      });
      this.loadCount++;
      if (this.loadCount === this.playlists.length) {
        return this.finishedLoading();
      }
    };

    PlaylistLoader.prototype.finishedLoading = function() {
      return $(window).trigger('playlistloader.finished', {
        playlists: this.data
      });
    };

    PlaylistLoader.coalescePlaylists = function(playlistsLoaded) {
      var fileEntry, output, playlist, _i, _j, _len, _len1;
      output = [];
      for (_i = 0, _len = playlistsLoaded.length; _i < _len; _i++) {
        playlist = playlistsLoaded[_i];
        for (_j = 0, _len1 = playlist.length; _j < _len1; _j++) {
          fileEntry = playlist[_j];
          output.push(fileEntry);
        }
      }
      return output;
    };

    return PlaylistLoader;

  })();

}).call(this);
;(function() {
  var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

  window.StreamUI = (function() {
    function StreamUI(selector, streamPlaylists) {
      this.selector = selector;
      this.streamPlaylists = streamPlaylists;
      this.playlistSelect = __bind(this.playlistSelect, this);
      this.playlistReady = __bind(this.playlistReady, this);
      this.container = jQuery(this.selector);
      this.playlist = new PlaylistUI(this.container.find('.playlist-ui'));
      this.player = new PlayerUI(this.container.find('.player-ui'));
      this.hookEvents();
      this.playlist.loadM3UList(this.streamPlaylists);
    }

    StreamUI.prototype.hookEvents = function() {
      var playlistUI;
      playlistUI = this.container.find('.playlist-ui');
      playlistUI.on('playlistui.ready', this.playlistReady);
      return playlistUI.on('playlistui.select', this.playlistSelect);
    };

    StreamUI.prototype.playlistReady = function(evt, eventinfo) {
      if (eventinfo.autoplay !== null) {
        return eventinfo.ui.select(eventinfo.autoplay);
      } else {
        return eventinfo.ui.selectFirst();
      }
    };

    StreamUI.prototype.playlistSelect = function(evt, eventinfo) {
      this.player.setMedia({
        mp3: eventinfo.item.file
      });
      return this.player.play();
    };

    return StreamUI;

  })();

}).call(this);

虽然我主要是一名 linux 开发人员,我的大部分编程经验都是使用 Perl 和 PHP,并且非常了解 jQuery 来处理我的 Web 开发,但在 jPlayer 甚至音频流方面我肯定是个新手。我希望有人能在上面的 hte 代码中发现一些可能导致我们在向我们的 Icecast 2.4.4 流添加介绍时遇到的问题?

我们的直播可以在下面的 URL 上找到,我现在有我们 HD4 直播的介绍。

streaming player

这个问题很容易通过启动流并听一点直到歌曲改变,拨打电话让它中断流,然后挂断来重复。这将导致在前奏之后再次播放第一首听过的歌曲。

我相信编解码器是匹配的,在我格式化为 MP3 128Kbps 比特率 44.1KHz 采样和 2 声道立体声之前,我确实遇到了让 intro 工作的问题。这是介绍文件信息:

user@stream:~$ mediainfo /usr/share/icecast2/web/high_quality.mp3
General
Complete name                            : /usr/share/icecast2/web/high_quality.mp3
Format                                   : MPEG Audio
File size                                : 138 KiB
Duration                                 : 8s 777ms
Overall bit rate mode                    : Constant
Overall bit rate                         : 128 Kbps
Writing library                          : LAME3.99r

Audio
Format                                   : MPEG Audio
Format version                           : Version 1
Format profile                           : Layer 3
Mode                                     : Joint stereo
Mode extension                           : MS Stereo
Duration                                 : 8s 803ms
Bit rate mode                            : Constant
Bit rate                                 : 128 Kbps
Channel(s)                               : 2 channels
Sampling rate                            : 44.1 KHz
Compression mode                         : Lossy
Stream size                              : 137 KiB (100%)
Writing library                          : LAME3.99r
Encoding settings                        : -m j -V 4 -q 3 -lowpass 17 -b 128

【问题讨论】:

    标签: mobile jplayer icecast


    【解决方案1】:

    听起来像底层浏览器缓存启动并强制重放内存中的某些内容。浏览器在某些情况下就像那样“很棒”,然后会不顾一切地忽略无缓存指令和其他东西。

    确保浏览器不会尝试播放缓存恶作剧的一种方法是添加“缓存破坏器”。本质上是一个查询字符串( /stream?foo=bar ),它使浏览器引擎认为它是动态生成的内容并丢弃它的缓存;参看。 https://www.keycdn.com/support/what-is-cache-busting.

    此时您的 Icecast 服务器似乎没有响应任何请求。所以我无法调查你这边的具体情况。

    【讨论】:

    • 他们在我们之前为流提供服务的两个路由器之一出现问题,但现在已修复。是的,我知道缓存有时会很困难。这种情况每次都会发生,而且在没有介绍活动的情况下永远不会发生。这也发生在我们的移动应用程序中。选择 WMNF HD4 频道将获得带有激活介绍的流。我确实尝试了建议的缓存破坏,但结果相同。
    • 将 intro 和 stream 的编解码器参数完全匹配。因为您似乎是在传统模式下进行流式传输,这是一个硬性要求。
    • 是的,我相信是这样,请参阅我添加的信息。不确定“传统模式”是什么意思?
    猜你喜欢
    • 1970-01-01
    • 2011-09-08
    • 1970-01-01
    • 1970-01-01
    • 2012-10-08
    • 2013-12-19
    • 2012-05-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多