video-js-hls_test.js 11.3 KB
(function(window) {
  /*
    ======== A Handy Little QUnit Reference ========
    http://api.qunitjs.com/

    Test methods:
      module(name, {[setup][ ,teardown]})
      test(name, callback)
      expect(numberOfAssertions)
      stop(increment)
      start(decrement)
    Test assertions:
      ok(value, [message])
      equal(actual, expected, [message])
      notEqual(actual, expected, [message])
      deepEqual(actual, expected, [message])
      notDeepEqual(actual, expected, [message])
      strictEqual(actual, expected, [message])
      notStrictEqual(actual, expected, [message])
      throws(block, [expected], [message])
  */
  var
	  manifestController,
	  segmentController,
	  m3u8parser,
    parser,

    expectedHeader = [
      0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00,
      0x09, 0x00, 0x00, 0x00, 0x00
    ],
    testAudioTag,
    testVideoTag,
    testScriptTag,
    asciiFromBytes,
    testScriptString,
    testScriptEcmaArray;

  module('environment');

  test('is sane', function() {
    expect(1);
    ok(true);
  });

  module('segment parser', {
    setup: function() {
      parser = new window.videojs.hls.SegmentParser();
    }
  });

  test('creates an flv header', function() {
    var header = Array.prototype.slice.call(parser.getFlvHeader());
    ok(header, 'the header is truthy');
    equal(9 + 4, header.length, 'the header length is correct');
    equal(header[0], 'F'.charCodeAt(0), 'the first character is "F"');
    equal(header[1], 'L'.charCodeAt(0), 'the second character is "L"');
    equal(header[2], 'V'.charCodeAt(0), 'the third character is "V"');

    deepEqual(expectedHeader, header, 'the rest of the header is correct');
  });

  test('parses the first bipbop segment', function() {
    var tag, bytes, i;
    parser.parseSegmentBinaryData(window.bcSegment);
    
    ok(parser.tagsAvailable(), 'tags are available');

    console.log('h264 tags:', parser.stats.h264Tags(),
                'aac tags:', parser.stats.aacTags());
  });

  testAudioTag = function(tag) {
    var
      byte = tag.bytes[11],
      format = (byte & 0xF0) >>> 4,
      soundRate = byte & 0x03,
      soundSize = (byte & 0x2) >>> 1,
      soundType = byte & 0x1,
      aacPacketType = tag.bytes[12];

    equal(10, format, 'the audio format is aac');
    equal(3, soundRate, 'the sound rate is 44kHhz');
    equal(1, soundSize, 'the sound size is 16-bit samples');
    equal(1, soundType, 'the sound type is stereo');

    ok(aacPacketType === 0 || aacPacketType === 1, 'aac packets should have a valid type');
  };

  testVideoTag = function(tag) {
    var
      byte = tag.bytes[11],
      frameType = (byte & 0xF0) >>> 4,
      codecId = byte & 0x0F,
      packetType = tag.bytes[12],
      compositionTime = (tag.view.getInt32(13) & 0xFFFFFF00) >> 8,
      nalHeader;

    // payload starts at tag.bytes[16]


    // XXX: I'm not sure that frame types 3-5 are invalid
    ok(frameType === 1 || frameType === 2,
       'the frame type should be valid');

    equal(7, codecId, 'the codec ID is AVC for h264');
    ok(packetType <= 2 && packetType >= 0, 'the packet type is within [0, 2]');
    if (packetType !== 1) {
      equal(0,
            compositionTime,
            'the composition time is zero for non-NALU packets');
    }
    
    // TODO: the rest of the bytes are an NLU unit
    if (packetType === 0) {
      // AVC decoder configuration record
    } else {
      // NAL units
      testNalUnit(tag.bytes.subarray(16));
    }
  };

  testNalUnit = function(bytes) {
    var
      nalHeader = bytes[0],
      unitType = nalHeader & 0x1F;

    equal(0, (nalHeader & 0x80) >>> 7, 'the first bit is always 0');
    // equal(90, (nalHeader & 0x60) >>> 5, 'the NAL reference indicator is something');
    // ok(unitType > 0, 'NAL unit type ' + unitType + ' is greater than 0');
    // ok(unitType < 22 , 'NAL unit type ' + unitType + ' is less than 22');
  };
  

  asciiFromBytes = function(bytes) {
    var
      string = [],
      i = bytes.byteLength;

    while (i--) {
      string[i] = String.fromCharCode(bytes[i]);
    }
    return string.join('');
  };

  testScriptString = function(tag, offset, expected) {
    var type = tag.bytes[offset],
        stringLength = tag.view.getUint16(offset + 1),
        string,
        i = expected.length;

    equal(2, type, 'the script element is of string type');
    equal(stringLength, expected.length, 'the script string length is correct');
    string = asciiFromBytes(tag.bytes.subarray(offset + 3,
                                               offset + 3 + stringLength));
    equal(expected, string, 'the string value is "' + expected + '"');
  };

  testScriptEcmaArray = function(tag, start) {
    var
      numItems = tag.view.getUint32(start),
      i = numItems,
      offset = start + 4,
      length,
      type;

    while (i--) {
      length = tag.view.getUint16(offset);

      // advance offset to the property value
      offset += 2 + length;

      type = tag.bytes[offset];
      ok(type === 1 || type === 0,
         'the ecma array property value type is number or boolean');
      offset++;
      if (type) {
        // boolean
        ok(tag.bytes[offset] === 0 || tag.bytes[offset] === 1,
           'the script boolean value is 0 or 1');
        offset++;
      } else {
        // number
        ok(!isNaN(tag.view.getFloat64(offset)), 'the value is not NaN');
        offset += 8;
      }
    }
    equal(tag.bytes[offset], 0, 'the property array terminator is valid');
    equal(tag.bytes[offset + 1], 0, 'the property array terminator is valid');
    equal(tag.bytes[offset + 2], 9, 'the property array terminator is valid');
  };

  testScriptTag = function(tag) {
    testScriptString(tag, 11, 'onMetaData');

    // the onMetaData object is stored as an 'ecma array', an array with non-
    // integer indices (i.e. a dictionary or hash-map).
    equal(8, tag.bytes[24], 'onMetaData is of ecma array type');
    testScriptEcmaArray(tag, 25);
  };

  test('the flv tags are well-formed', function() {
    var
      tag,
      byte,
      type,
      lastTime = 0;
    parser.parseSegmentBinaryData(window.bcSegment);

    while (parser.tagsAvailable()) {
      tag = parser.getNextTag();
      type = tag.bytes[0];

      // generic flv headers
      ok(type === 8 || type === 9 || type === 18,
         'the type field specifies audio, video or script');
      
      byte = (tag.view.getUint32(1) & 0xFFFFFF00) >>> 8;
      equal(tag.bytes.byteLength - 11 - 4, byte, 'the size field is correct');

      byte = tag.view.getUint32(5) & 0xFFFFFF00;
      ok(byte >= lastTime, 'the timestamp for the tag is greater than zero');
      lastTime = byte;

      // tag type-specific headers
      ({
        8: testAudioTag,
        9: testVideoTag,
        18: testScriptTag
      })[type](tag);

      // previous tag size
      equal(tag.bytes.byteLength - 4,
            tag.view.getUint32(tag.bytes.byteLength - 4),
            'the size of the previous tag is correct');
    }
  });

	/*
		M3U8 Test Suite
	 */

  module('m3u8 parser', {
    setup: function() {
	    m3u8parser = new window.videojs.hls.M3U8Parser();
    }
  });

	test('should create my parser', function() {
		 ok(m3u8parser != undefined);
		}
	);

	test('should successfully parse manifest data', function() {
			var parsedData = m3u8parser.parse(window.playlistData);
			ok(parsedData);
		}
	);

	test('test for expected results', function() {
			var data = m3u8parser.parse(window.playlistData);

			notEqual(data, null, 'data is not NULL');
			equal(data.invalidReasons.length, 0,'data has 0 invalid reasons');
			equal(data.hasValidM3UTag, true, 'data has valid EXTM3U');
			equal(data.targetDuration, 10, 'data has correct TARGET DURATION');
			equal(data.allowCache, "NO", 'acceptable ALLOW CACHE');
			equal(data.isPlaylist, true, 'data is parsed as a PLAYLIST as expected');
			equal(data.playlistType, "VOD", 'acceptable PLAYLIST TYPE');
			equal(data.mediaItems.length, 16, 'acceptable mediaItem count');
			equal(data.mediaSequence, 0, 'MEDIA SEQUENCE is correct');
			equal(data.totalDuration, -1, "ZEN TOTAL DURATION is unknown as expected");
			equal(data.hasEndTag, true, 'should have ENDLIST tag');
		}
	);

	module('brightcove playlist', {
		setup: function() {
			m3u8parser = new window.videojs.hls.M3U8Parser();
		}
	});

	test('should parse a brightcove manifest data', function() {
		  var data = m3u8parser.parse(window.brightcove_playlist_data);

			ok(data);
			equal(data.playlistItems.length, 4, 'Has correct rendition count');
			equal(data.playlistItems[0].bandwidth, 240000, 'First rendition index bandwidth is correct' );
			equal(data.playlistItems[0]["program-id"], 1, 'First rendition index program-id is correct' );
			equal(data.playlistItems[0].resolution.width, 396, 'First rendition index resolution width is correct' );
			equal(data.playlistItems[0].resolution.height, 224, 'First rendition index resolution height is correct' );

		}
	);

	module('manifest controller', {
		setup: function() {
			manifestController = new window.videojs.hls.ManifestController();
			this.vjsget = vjs.get;
			vjs.get = function(url, success, error){
				console.log(url);
				success(window.brightcove_playlist_data);
			};
		},
		teardown: function() {
			vjs.get = this.vjsget;
		}
	});

	test('should create', function() {
		ok(manifestController);
	});

	test('should return a parsed object', function() {
		var data = manifestController.parseManifest(window.brightcove_playlist_data);

		ok(data);

		equal(data.playlistItems.length, 4, 'Has correct rendition count');
		equal(data.playlistItems[0].bandwidth, 240000, 'First rendition index bandwidth is correct' );
		equal(data.playlistItems[0]["program-id"], 1, 'First rendition index program-id is correct' );
		equal(data.playlistItems[0].resolution.width, 396, 'First rendition index resolution width is correct' );
		equal(data.playlistItems[0].resolution.height, 224, 'First rendition index resolution height is correct' );
	})

	test('should get a manifest from hermes', function() {
		var hermesUrl = "http://localhost:7070/test/basic-playback/brightcove/16x9-master.m3u8";

		manifestController.loadManifest(
			hermesUrl,
			function(responseData){
				ok(true);
			},
			function(errorData){
				console.log('got error data');
			},
			function(updateData){
				console.log('got update data');
			}
		)
	});

	module('segment controller', {
		setup: function() {
			segmentController = new window.videojs.hls.SegmentController();
			this.vjsget = vjs.get;
			vjs.get = function(url, success, error){
				console.log('load segment url', url);
				success(window.bcSegment);
			};
		},
		teardown: function() {
			vjs.get = this.vjsget;
		}
	});

	test('should get a segment data', function() {
			ok(true);
			var hermesUrl = "http://localhost:7070/test/ts-files/brightcove/s-1.ts";

			segmentController.loadSegment(
				hermesUrl,
				function(responseData){
					console.log('got response from segment controller');
					ok(true);

				},
				function(errorData){
					console.log('got error data');
				},
				function(updateData){
					console.log('got update data');
				}
			)
		}
	)

	test('bandwidth calulation test', function() {
		var multiSecondData = segmentController.calculateThroughput(10000,1000,2000);
		var subSecondData = segmentController.calculateThroughput(10000,1000,1500);
		equal(multiSecondData, 80000, 'MULTI-Second bits per second calculation');
		equal(subSecondData, 160000, 'SUB-Second bits per second calculation');

	})

})(this);