How to change bitrate based on internet speed in JW player

997 views Asked by At

Is this possible using this code: jwplayer().getQualityLevels()

1

There are 1 answers

0
barsh On

Based on your question I suppose you aren't using HLS or DASH but instead you have several MP4s that have been encoded at different bitrates or resolutions. The MP4's need to be properly encoded for progressive download support and the http server needs to support byte-range headers. It's possible to monitor jwplayer's buffer and when it gets too low, you can switch to a lower quality source. We do this as www.scormfly.com. Here's some typescript code that could get you started.

import * as _ from 'lodash'
import Controls, { StreamingMode } from '../controls'
const controls = new Controls()

export default class ProgressiveDownloadProvider {
  private positionBeforeQualityChange = 0
  private isSeeking: boolean
  private jwplayer: JWPlayer
  constructor(jwplayer: JWPlayer) {
    jwplayer.on('levels', this.setToBestFitQuality)
    jwplayer.on('levelsChanged', this.onQualityChange)
    jwplayer.on('time', this.tick)

    this.jwplayer = jwplayer
  }

  public tick(args: TimeParam) {
    if (this.isQualityTooHigh(args.position)) {
      this.reduceQualityLevel()
    }
  }
  public onResize() {
    // don't change the quality unless the the video is playing
    // because changing the quality will start playing the video
    if (this.jwplayer.getState().toUpperCase() === 'PLAYING') {
      this.setToBestFitQuality()
    }
  }

  private isQualityTooHigh(currentPosition: number): boolean {
    if (this.isSeeking) { return false }
    if (currentPosition < this.positionBeforeQualityChange + 1) { return false }

    const videoElement = $('video')[0]
    const bufferPositionInSeconds = videoElement ?
      (videoElement as any).buffered.end((videoElement as any).buffered.length - 1) :
      this.jwplayer.getBuffer() / 100.0 * controls.getDuration()
    const bufferAlmostComplete = bufferPositionInSeconds > controls.getDuration() - 10
    if (bufferAlmostComplete) { return false }

    const bufferRunningLow = bufferPositionInSeconds - currentPosition < 0.5
    return bufferRunningLow
  }

  private reduceQualityLevel() {
    // qualityLevels are indexed 0-best to 5-worst quality
    const qualityLevels = this.jwplayer.getQualityLevels()
    const indexOfWorstQuality = qualityLevels.length - 1
    const indexOfCurrentQuality = this.jwplayer.getCurrentQuality()
    if (indexOfCurrentQuality === indexOfWorstQuality) { return }

    // tslint:disable-next-line:no-console
    console.log('reducing quality from '
      + qualityLevels[indexOfCurrentQuality].label + ' to '
      + qualityLevels[indexOfCurrentQuality + 1].label + ' due to buffering')
    this.jwplayer.setCurrentQuality(indexOfCurrentQuality + 1)
  }

  // detect viewport height and use the best fitting source
  private setToBestFitQuality() {
    if (controls.getStreamingMode() === StreamingMode.YouTube) { return } // don't adjust youtube videos
    if (typeof this.jwplayer.getQualityLevels() === 'undefined') { return } // not ready yet
    if (this.jwplayer.getQualityLevels().length === 0) { return } // nothing to do
    if (this.jwplayer.getCurrentQuality() === -1) { return } // this can happen onComplete

    const newHeight = $('#jwPlayer').height()

    const currentQuality = this.jwplayer.getQualityLevels()[this.jwplayer.getCurrentQuality()].label // eg 720p HD

    let optimalQuality: string
    const heights = _.map(this.jwplayer.getQualityLevels(), (item) => item.label.replace('p', ''))
    for (const height of heights) {
      const tooBig = height > newHeight
      if (tooBig) {
        break
      }
      optimalQuality = height + 'p'
    }

    if (optimalQuality !== this.jwplayer.getQualityLevels()[this.jwplayer.getCurrentQuality()].label) {
      // tslint:disable-next-line:no-console
      console.log('Switching quality to '
        + optimalQuality + ' because window will fit up to '
        + $(window).height() + 'p')

      // find desired quality level and use it
      const levels = this.jwplayer.getQualityLevels()
      for (let j = 0; j < levels.length; j++) {
        if (levels[j].label === optimalQuality) {
          this.jwplayer.setCurrentQuality(j)
        }
      }
    }
  }

  // preserve playback position
  private onQualityChange() {
    this.positionBeforeQualityChange = this.jwplayer.getPosition()
    setTimeout(() => {
      controls.seek(this.positionBeforeQualityChange)
    }, 500)
  }

}