├── .actionScriptProperties ├── .gitignore ├── .npmignore ├── .project ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── contrib.json ├── dist ├── video-js.swf ├── video-js.swf.old └── video-js.swf.orig ├── index.html ├── libs ├── flexunit-4.1.0-8-as3_4.1.0.16076.swc ├── flexunit-4.1.0-8-flex_4.1.0.16076.swc ├── flexunit-aircilistener-4.1.0-8-4.1.0.16076.swc ├── flexunit-cilistener-4.1.0-8-4.1.0.16076.swc ├── flexunit-flexcoverlistener-4.1.0-8-4.1.0.16076.swc ├── flexunit-uilistener-4.1.0-8-4.1.0.16076.swc └── fluint-extensions-4.1.0-8-4.1.0.16076.swc ├── package.json ├── sandbox └── videojs.html.example ├── src ├── AirTestRunner.mxml ├── VideoJS.as └── com │ └── videojs │ ├── Base64.as │ ├── VideoJSApp.as │ ├── VideoJSModel.as │ ├── VideoJSView.as │ ├── events │ ├── VideoErrorEvent.as │ ├── VideoJSEvent.as │ └── VideoPlaybackEvent.as │ ├── providers │ ├── HTTPAudioProvider.as │ ├── HTTPVideoProvider.as │ ├── IProvider.as │ └── RTMPVideoProvider.as │ ├── structs │ ├── ExternalErrorEventName.as │ ├── ExternalEventName.as │ ├── PlaybackType.as │ └── PlayerMode.as │ └── test │ ├── VideoJSModelTest.as │ └── suite │ └── VideoJSTestSuite.as └── tests ├── index.html ├── manual ├── css │ ├── reset.css │ └── styles.css ├── img │ └── testPattern_ussr_480x360.png ├── index.html ├── js │ └── swfobject.js ├── manual.html └── manual_full.html ├── qunit ├── .gitignore ├── README.md ├── addons │ ├── canvas │ │ ├── README.md │ │ ├── canvas-test.js │ │ ├── canvas.html │ │ └── qunit-canvas.js │ ├── close-enough │ │ ├── README.md │ │ ├── close-enough-test.js │ │ ├── close-enough.html │ │ └── qunit-close-enough.js │ ├── composite │ │ ├── README.md │ │ ├── composite-demo-test.html │ │ ├── composite-test.html │ │ ├── composite-test.js │ │ ├── dummy-qunit-test.html │ │ ├── dummy-same-test.html │ │ ├── index.html │ │ ├── qunit-composite.css │ │ └── qunit-composite.js │ └── step │ │ ├── README.md │ │ ├── qunit-step.js │ │ ├── step-test.js │ │ └── step.html ├── package.json ├── qunit │ ├── qunit.css │ └── qunit.js └── test │ ├── headless.html │ ├── index.html │ ├── logs.html │ ├── logs.js │ ├── same.js │ └── test.js ├── test.html └── test.js /.actionScriptProperties: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bin-release 3 | bin-debug 4 | *.iml 5 | http/node_modules 6 | node_modules 7 | dist/* 8 | build 9 | 10 | *.orig 11 | 12 | sandbox/* 13 | !sandbox/*.example -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Exclude everything but the contents of the dist directory. 2 | **/* 3 | !dist/** 4 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | VideoJS 4 | 5 | 6 | 7 | 8 | 9 | com.adobe.flexbuilder.project.flexbuilder 10 | 11 | 12 | 13 | 14 | 15 | com.adobe.flexbuilder.project.actionscriptnature 16 | 17 | 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | ## HEAD (Unreleased) 5 | * Add license to package.json 6 | 7 | -------------------- 8 | 9 | ## 5.4.2 (2018-09-20) 10 | * Fix video dimensions pinned to 100x100 11 | 12 | ## 5.4.1 (2017-07-25) 13 | * chore: update broken URLs 14 | * fix: decouple muted() from volume=0 15 | 16 | ## 5.4.0 (2017-04-28) 17 | * Add getVideoPlaybackQuality API 18 | * Fix buffered() getter for RTMP provider 19 | * Fix paused state for RTMP provider after video ends 20 | 21 | ## 5.3.0 (2017-02-17) 22 | * @albertogasparin added FCsubscribe call and proxy type to RTMP video provider 23 | * @mjneil add callback to adjust currentTime for video provider 24 | 25 | ## 5.2.0 (2017-02-07) 26 | * @mjneil added appendChunkReady, another way of passing data into the swf 27 | 28 | ## 5.1.0 (2016-07-18) 29 | * @alex-philips added support for the onTextData event 30 | 31 | ## 5.0.3 (2016-05-31) 32 | * Fix `muted` and `loop` attributes not being passed to the swf ([view](https://github.com/videojs/video-js-swf/pull/205)) 33 | 34 | ## 5.0.2 (2016-05-06) 35 | * If we are in data generation mode and even if `_playbackStarted` hasn't happened yet, we should still set `_isSeeking` to true so that we can correctly emit `seeked` after an initial (before playback) seek ([view](https://github.com/videojs/video-js-swf/pull/204)) 36 | 37 | ## 5.0.1 (2015-11-06) 38 | * Fix an issue where the player would not report is was seeking after ending ([view](https://github.com/videojs/video-js-swf/pull/192)) 39 | 40 | ## 5.0.0 (2015-10-28) 41 | * Return an empty set of seekable time ranges when seeking in data generation mode ([view](https://github.com/videojs/video-js-swf/pull/187)) 42 | 43 | ## 5.0.0-rc1 (2015-07-27) 44 | * Don't fire loadstart or loadedmetadata in data generation mode ([view](https://github.com/videojs/video-js-swf/pull/178)) 45 | * Remove unused poster support ([view](https://github.com/videojs/video-js-swf/pull/182)) 46 | * Expose buffered as ranges, not just an end point ([view](https://github.com/videojs/video-js-swf/pull/180)) 47 | * Do not seek to to the beginning after a video ends ([view](https://github.com/videojs/video-js-swf/pull/172)) 48 | 49 | ## 5.0.0-rc0 (2015-07-21) 50 | * Let javascript fire "seeking" instead of handling it in the SWF ([view](https://github.com/videojs/video-js-swf/pull/171)) 51 | 52 | ## 4.7.2 (2015-06-30) 53 | * Fixed an issue where an image from previous video could flash briefly while a new video is loaded ([view](https://github.com/videojs/video-js-swf/pull/167)) 54 | 55 | ## 4.7.1 (2015-06-23) 56 | * Fixed an issue where playback required two clisk to start when preload was not auto 57 | * @qpSHiNqp fix issue that would cause incorrect aspect ratios for some videos ([view](https://github.com/videojs/video-js-swf/pull/165)) 58 | 59 | ## 4.7.0 (2015-05-19) 60 | * @bc-bbay the preload attribute should be a string, not a boolean ([view](https://github.com/videojs/video-js-swf/pull/160)) 61 | * @Wellming fix manual tests ([view](https://github.com/videojs/video-js-swf/pull/154)) 62 | 63 | ## 4.6.1 (2015-04-22) 64 | * @bclwhitaker append END_SEQUENCE properly in data generation mode ([view](https://github.com/videojs/video-js-swf/pull/152)) 65 | 66 | ## 4.6.0 (2015-04-16) 67 | * Add vjs_discontinuity ([view](https://github.com/videojs/video-js-swf/pull/150)) 68 | * Don't call resume() on NetstreamPlayStart ([view](https://github.com/videojs/video-js-swf/pull/147)) 69 | 70 | ## 4.5.4 (2015-03-17) 71 | * Improved handling of the paused state, and the loadstart and canplay events ([view](https://github.com/videojs/video-js-swf/pull/139)) 72 | * Fixed a potential XSS issue with the swf event callbacks ([view](https://github.com/videojs/video-js-swf/pull/143)) 73 | * Prevented pause from firing after eneded ([view](https://github.com/videojs/video-js-swf/pull/144)) 74 | 75 | ## 4.5.3 (2015-01-22) 76 | * Paused should be true before a source has been set by default 77 | 78 | ## 4.5.2 (2014-12-04) 79 | * Fixed an issue where Flash would crash when switching sources quickly ([view](https://github.com/videojs/video-js-swf/pull/131)) 80 | 81 | ## 4.5.1 (2014-10-15) 82 | * Fixed an issue where changing the source immediately after seeking could cause an error ([view](https://github.com/videojs/video-js-swf/pull/125)) 83 | * Added sanitation for all data that might be passed through the external interface ([view](https://github.com/videojs/video-js-swf/pull/127)) 84 | 85 | ## 4.5.0 (2014-09-29) 86 | * Buffering and playback event fixes ([view](https://github.com/videojs/video-js-swf/pull/122)) 87 | 88 | ## 4.4.5 (2014-09-25) 89 | * Fixed sanitation of URLs to special case blob URLs ([view](https://github.com/videojs/video-js-swf/pull/121)) 90 | 91 | ## 4.4.4 (2014-09-22) 92 | * Added sanitizing of the src param ([view](https://github.com/videojs/video-js-swf/pull/120)) 93 | 94 | ## 4.4.3 (2014-08-14) 95 | * Rebuild with Flash target-player 10.3 and swf-version 12. ([view](https://github.com/videojs/video-js-swf/issues/113)) 96 | 97 | ## 4.4.2 (2014-07-11) 98 | * Fixed networkState reporting to be more accurate after loadstart ([view](https://github.com/videojs/video-js-swf/pull/106)) 99 | 100 | ## 4.4.1 (2014-06-11) 101 | * Ignore unnecessary files from npm packaging ([view](https://github.com/videojs/video-js-swf/pull/87)) 102 | * Fixed bug triggering `playing` ([view](https://github.com/videojs/video-js-swf/pull/90)) 103 | * Fixed bug with the timing of `loadstart` ([view](https://github.com/videojs/video-js-swf/pull/93)) 104 | * Added support for clearing the NetStream while in Data Generation Mode ([view](https://github.com/videojs/video-js-swf/pull/93)) 105 | * Fixed silent exception when opening MediaSources ([view](https://github.com/videojs/video-js-swf/pull/97)) 106 | 107 | ## 4.4.0 (2014-02-18) 108 | * Added changelog 109 | * Added support for using NetStream in Data Generation Mode ([view](https://github.com/videojs/video-js-swf/pull/80)) 110 | * Extended base support for external appendData for integration with HLS / Media Source plugins ([view](https://github.com/videojs/video-js-swf/pull/80)) 111 | * Fixed bug with viewport sizing on videos which don't present meta data ([view](https://github.com/videojs/video-js-swf/pull/80)) 112 | * Fixed bugs with buffered and duration reporting on non-linear streams ([view](https://github.com/videojs/video-js-swf/pull/80)) 113 | * Added refined seeking for use on non-linear streams ([view](https://github.com/videojs/video-js-swf/pull/80)) 114 | * Extended endOfStream for use with Media Sources API ([view](https://github.com/videojs/video-js-swf/pull/80)) 115 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | If you're on this page, you must be interested in spending some time giving back to this humble project. If that's the case, here are some ways you can help build the future of the Video.js SWF: 2 | 3 | - Features and changes 4 | - Bug reports and fixes 5 | - Answer questions on Stack Overflow 6 | - Related Video.js projects 7 | 8 | ##Getting Started 9 | 10 | 1. Fork the video-js-swf git repository. At the top of every github page, there is a Fork button. Click it, and the forking process will copy video-js-swf into your organization. You can find more information on Forking a Github repository here. 11 | 12 | 2. Clone your fork of the video-js-swf repo, and assign the original repo to a remote called "upstream" 13 | ```bash 14 | git clone https://github.com//video-js-swf.git 15 | cd video-js-swf 16 | git remote add upstream https://github.com/videojs/video-js-swf.git 17 | ``` 18 | In the future, if you want to pull in updates to video-js-swf that happened after you cloned the main repo, you can run: 19 | ```bash 20 | git checkout master 21 | git pull upstream master 22 | ``` 23 | 24 | 3. Depending on whether you're adding something new, making a change or fixing a bug, you'll want to do some up-front preparation. 25 | 26 | If you're adding new functionality, you only need to create a new branch for your work. When you submit a Pull Request, Github automatically opens a new issue to track it. 27 | 28 | If you're fixing a bug, please submit an issue for it. If you're fixing an existing bug, claim it by adding a comment to it. This will give a heads-up to anyone watching the issue that you're working on a fix. Please refer to the [Filing Bugs](#bugs) section below for some guidelines on filing new issues. 29 | 30 | 4. Create a new branch for your work, and make your changes. 31 | 32 | 5. Thoroughly test your feature or fix. See the guide for running the unit and integration tests in the README. Adding new tests is both highly encouraged and appreciated. In addition to the automated tests, and testing the area(s) specific to the area you're working on, a brief smoketest of the player never hurts. We'd like to suggest this short smoke test with both FLV and MP4 video formats: 33 | 1. Playback should start after clicking Play overlay 34 | 2. Playback should start after clicking Play button 35 | 3. Playback should pause when clicking the Pause button 36 | 4. Seeking should work by clicking in the timeline 37 | 5. Seeking should work by dragging the scrubber in the timeline 38 | 6. Full screen (and restore from Full-screen) should work 39 | 7. Replay (without refreshing the browser) should work 40 | 41 | (This may seem like a lot of steps, but they could all be accomplished with a couple of short-duration test files, within a few minutes.) 42 | 43 | 6. After committing your changes to your fork of the repo, submit your [Pull Request](#pull-requests). If you are changing the source code, make sure to submit a second pull request to the main Video.js repo. This must be done to change the copy of video-js.swf that Video.js uses. When you run build.sh, video-js.swf is always updated to contain the latest changes you have in video-js-swf. 44 | 45 | ##Filing Bugs 46 | 47 | A bug is a demonstrable problem that is caused by the code in the repository. Good bug reports are extremely helpful. Thank You! 48 | 49 | Guidelines for bug reports: 50 | 51 | - Use the GitHub issue search — check if the issue has already been reported. 52 | - Check if the issue has been fixed — try to reproduce it using the latest master branch in the repository. 53 | - Isolate the problem — ideally create a reduced test case and a live example. 54 | 55 | A good bug report should be as detailed as possible, so that others won't have to follow up for the essential details. 56 | 57 | Here's an example: 58 | 59 | Summary: Short yet concise Bug Summary 60 | 61 | Description: Happens on Windows 7 and OSX. Seen with IE9, Firefox 19 OSX, Chrome 21, Flash 11.6 and 11.2 62 | 63 | This is the first step 64 | This is the second step 65 | Further steps, etc. 66 | 67 | Expected: (describe the expected outcome of the steps above) 68 | 69 | Actual: (describe what actually happens) 70 | 71 | (a link to the reduced test case, if it exists) 72 | 73 | Any other information you want to share that is relevant to the issue being reported. This might include the lines of code that you have identified as causing the bug, and potential solutions (and your opinions on their merits). 74 | 75 | ####NOTE: Testing Flash Locally in Chrome 76 | 77 | Chrome 21+ (as of 2013/01/01) doens't run Flash files that are local and loaded into a locally accessed page (file:///). To get around this you can do either of the following: 78 | 79 | 1. Do your development and testing using a local HTTP server. See the README for instructions on using simple-http-server for this purpose. 80 | 2. Disable the version of Flash included with Chrome and enable a system-wide version of Flash instead. 81 | 82 | Other Video.js Projects 83 | Video.js - Our open source HTML5 & Flash video player. 84 | Videojs.com - The public site with helpful tools and information about Video.js. 85 | 86 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON('package.json'), 5 | connect: { 6 | dev: { 7 | port: 8000, 8 | base: '.' 9 | } 10 | }, 11 | mxmlc: { 12 | options: { 13 | // http://livedocs.adobe.com/flex/3/html/help.html?content=compilers_16.html 14 | metadata: { 15 | // `-title "Adobe Flex Application"` 16 | title: 'VideoJS SWF', 17 | // `-description "http://www.adobe.com/flex"` 18 | description: 'http://www.videojs.com', 19 | // `-publisher "The Publisher"` 20 | publisher: 'Brightcove, Inc.', 21 | // `-creator "The Author"` 22 | creator: 'Brightcove, Inc.', 23 | // `-language=EN` 24 | // `-language+=klingon` 25 | language: 'EN', 26 | // `-localized-title "The Color" en-us -localized-title "The Colour" en-ca` 27 | localizedTitle: null, 28 | // `-localized-description "Standardized Color" en-us -localized-description "Standardised Colour" en-ca` 29 | localizedDescription: null, 30 | // `-contributor "Contributor #1" -contributor "Contributor #2"` 31 | contributor: null, 32 | // `-date "Mar 10, 2013"` 33 | date: null 34 | }, 35 | 36 | // http://livedocs.adobe.com/flex/3/html/help.html?content=compilers_18.html 37 | application: { 38 | // `-default-size 240 240` 39 | layoutSize: { 40 | width: 640, 41 | height: 360 42 | }, 43 | // `-default-frame-rate=24` 44 | frameRate: 30, 45 | // `-default-background-color=0x869CA7` 46 | backgroundColor: 0x000000, 47 | // `-default-script-limits 1000 60` 48 | scriptLimits: { 49 | maxRecursionDepth: 1000, 50 | maxExecutionTime: 60 51 | } 52 | }, 53 | 54 | // http://livedocs.adobe.com/flex/3/html/help.html?content=compilers_19.html 55 | // `-library-path+=libraryPath1 -library-path+=libraryPath2` 56 | libraries: ['libs/*.*'], 57 | // http://livedocs.adobe.com/flex/3/html/help.html?content=compilers_14.html 58 | // http://livedocs.adobe.com/flex/3/html/help.html?content=compilers_17.html 59 | // http://livedocs.adobe.com/flex/3/html/help.html?content=compilers_20.html 60 | // http://livedocs.adobe.com/flex/3/html/help.html?content=compilers_21.html 61 | compiler: { 62 | // `-accessible=false` 63 | 'accessible': false, 64 | // `-actionscript-file-encoding=UTF-8` 65 | 'actionscriptFileEncoding': null, 66 | // `-allow-source-path-overlap=false` 67 | 'allowSourcePathOverlap': false, 68 | // `-as3=true` 69 | 'as3': true, 70 | // `-benchmark=true` 71 | 'benchmark': true, 72 | // `-context-root context-path` 73 | 'contextRoot': null, 74 | // `-debug=false` 75 | 'debug': false, 76 | // `-defaults-css-files filePath1 ...` 77 | 'defaultsCssFiles': [], 78 | // `-defaults-css-url http://example.com/main.css` 79 | 'defaultsCssUrl': null, 80 | // `-define=CONFIG::debugging,true -define=CONFIG::release,false` 81 | // `-define+=CONFIG::bool2,false -define+=CONFIG::and1,"CONFIG::bool2 && false" 82 | // `-define+=NAMES::Company,"'Adobe Systems'"` 83 | 'defines': {}, 84 | // `-es=true -as3=false` 85 | 'es': false, 86 | // `-externs className1 ...` 87 | 'externs': [], 88 | // `-external-library-path+=pathElement` 89 | 'externalLibraries': [], 90 | 'fonts': { 91 | // `-fonts.advanced-anti-aliasing=false` 92 | advancedAntiAliasing: false, 93 | // `-fonts.languages.language-range "Alpha and Plus" "U+0041-U+007F,U+002B"` 94 | // USAGE: 95 | // ``` 96 | // languages: [{ 97 | // lang: 'Alpha and Plus', 98 | // range: ['U+0041-U+007F', 'U+002B'] 99 | // }] 100 | // ``` 101 | languages: [], 102 | // `-fonts.local-fonts-snapsnot filePath` 103 | localFontsSnapshot: null, 104 | // `-fonts.managers flash.fonts.JREFontManager flash.fonts.BatikFontManager flash.fonts.AFEFontManager` 105 | // NOTE: FontManager preference is in REVERSE order (prefers LAST array item). 106 | // For more info, see http://livedocs.adobe.com/flex/3/html/help.html?content=fonts_06.html 107 | managers: [] 108 | }, 109 | // `-incremental=false` 110 | 'incremental': false 111 | } 112 | }, 113 | videojs_swf: { 114 | files: { 115 | 'dist/video-js.swf': ['src/VideoJS.as'] 116 | } 117 | } 118 | }, 119 | bumpup: { 120 | options: { 121 | updateProps: { 122 | pkg: 'package.json' 123 | } 124 | }, 125 | file: 'package.json' 126 | }, 127 | tagrelease: { 128 | file: 'package.json', 129 | commit: true, 130 | message: 'Release %version%', 131 | prefix: 'v' 132 | }, 133 | shell: { 134 | options: { 135 | failOnError: true 136 | }, 137 | 'git-diff-exit-code': { command: 'git diff --exit-code' }, 138 | 'git-diff-cached-exit-code': { command: 'git diff --cached --exit-code' }, 139 | 'git-add-dist-force': { command: 'git add dist --force' }, 140 | 'git-merge-stable': { command: 'git merge stable' }, 141 | 'git-merge-master': { command: 'git merge master' }, 142 | 'git-checkout-stable': { command: 'git checkout stable' }, 143 | 'git-checkout-master': { command: 'git checkout master' }, 144 | 'git-push-origin-stable': { command: 'git push origin stable' }, 145 | 'git-push-upstream-stable': { command: 'git push upstream stable' }, 146 | 'git-push-origin-master': { command: 'git push origin master' }, 147 | 'git-push-upstream-master': { command: 'git push upstream master' }, 148 | 'git-push-upstream-tags': { command: 'git push upstream --tags' } 149 | }, 150 | prompt: { 151 | release: { 152 | options: { 153 | questions: [ 154 | { 155 | config: 'release', // arbitray name or config for any other grunt task 156 | type: 'confirm', // list, checkbox, confirm, input, password 157 | message: 'You tested and merged the changes into stable?', 158 | default: false, // default value if nothing is entered 159 | // choices: 'Array|function(answers)', 160 | // validate: function(value){ console.log('hi', value); grunt.fatal('test'); return "error"; }, // return true if valid, error message if invalid 161 | // filter: function(value), // modify the answer 162 | // when: function(answers) // only ask this question when this function returns true 163 | } 164 | ] 165 | } 166 | }, 167 | } 168 | }); 169 | 170 | grunt.loadNpmTasks('grunt-connect'); 171 | grunt.loadNpmTasks('grunt-bumpup'); 172 | grunt.loadNpmTasks('grunt-tagrelease'); 173 | grunt.loadNpmTasks('grunt-npm'); 174 | grunt.loadNpmTasks('grunt-shell'); 175 | grunt.loadNpmTasks('grunt-prompt'); 176 | grunt.loadNpmTasks('chg'); 177 | 178 | grunt.registerTask('dist', ['mxmlc']); 179 | grunt.registerTask('default', ['dist']); 180 | 181 | grunt.registerMultiTask('mxmlc', 'Compiling SWF', function () { 182 | // Merge task-specific and/or target-specific options with these defaults. 183 | var childProcess = require('child_process'); 184 | var flexSdk = require('flex-sdk'); 185 | var async = require('async'); 186 | var pkg = grunt.file.readJSON('package.json'); 187 | 188 | var 189 | options = this.options, 190 | done = this.async(), 191 | maxConcurrency = 1, 192 | q, 193 | workerFn; 194 | 195 | workerFn = function(f, callback) { 196 | // Concat specified files. 197 | var srcList = f.src.filter(function(filepath) { 198 | // Warn on and remove invalid source files (if nonull was set). 199 | if (!grunt.file.exists(filepath)) { 200 | grunt.log.error('Source file "' + filepath + '" not found.'); 201 | return false; 202 | } 203 | else { 204 | return true; 205 | } 206 | }); 207 | 208 | var cmdLineOpts = []; 209 | 210 | if (f.dest) { 211 | cmdLineOpts.push('-output'); 212 | cmdLineOpts.push(f.dest); 213 | } 214 | 215 | cmdLineOpts.push('-define=CONFIG::version, "' + pkg.version + '"'); 216 | cmdLineOpts.push('--'); 217 | cmdLineOpts.push.apply(cmdLineOpts, srcList); 218 | 219 | grunt.verbose.writeln('package version: ' + pkg.version); 220 | grunt.verbose.writeln('mxmlc path: ' + flexSdk.bin.mxmlc); 221 | grunt.verbose.writeln('options: ' + JSON.stringify(cmdLineOpts)); 222 | 223 | // Compile! 224 | childProcess.execFile(flexSdk.bin.mxmlc, cmdLineOpts, function(err, stdout, stderr) { 225 | if (!err) { 226 | grunt.log.writeln('File "' + f.dest + '" created.'); 227 | } 228 | else { 229 | grunt.log.error(err.toString()); 230 | grunt.verbose.writeln('stdout: ' + stdout); 231 | grunt.verbose.writeln('stderr: ' + stderr); 232 | 233 | if (options.force === true) { 234 | grunt.log.warn('Should have failed but will continue because this task had the `force` option set to `true`.'); 235 | } 236 | else { 237 | grunt.fail.warn('FAILED'); 238 | } 239 | 240 | } 241 | callback(err); 242 | }); 243 | }; 244 | 245 | q = async.queue(workerFn, maxConcurrency); 246 | q.drain = done; 247 | q.push(this.files); 248 | }); 249 | 250 | /** 251 | * How releases work: 252 | * 253 | * Changes come from pullrequests to master or stable. 254 | * They are tested then pulled into their base branch. 255 | * A change log item is added to "Unreleased". 256 | * In a minor/major release, master is merged into stable 257 | * (possibly by way of a release branch if testing more). 258 | * 259 | * Check out stable if not already checked out. 260 | * Run `grunt release:RELEASE_TYPE` 261 | * RELEASE_TYPE = major, minor, or patch 262 | * Does the following: 263 | * Bump version 264 | * Build dist 265 | * Force add dist 266 | * Rotate changelog 267 | * Commit changes 268 | * Tag release 269 | * 270 | * Staging should be merged back into master. 271 | * Push stable and master to origin. 272 | * Run `npm publish`. 273 | */ 274 | grunt.registerTask('release', 'Bump, build, and tag', function(type) { 275 | // major, minor, patch 276 | type = type ? type : 'patch'; 277 | 278 | grunt.task.run([ 279 | 'prompt-release', // make sure user is ready 280 | 'shell:git-diff-exit-code', // ensure there's no unadded changes 281 | 'shell:git-diff-cached-exit-code', // ensure there's no added changes 282 | 'shell:git-checkout-stable', // must start on the stable branch 283 | 'chg-release:'+type, // add release to changelog 284 | 'bumpup:'+type, // bump up the package version 285 | 'dist', // build distribution 286 | 'shell:git-add-dist-force', // force add the distribution 287 | 'tagrelease', // commit & tag the changes 288 | 'shell:git-push-origin-stable', // push changes to your fork 289 | 'shell:git-push-upstream-stable', // push changes to upstream 290 | 'shell:git-push-upstream-tags', // push version tag 291 | 'npm-publish', // publish to npm 292 | 'shell:git-checkout-master', // switch to master branch 293 | 'shell:git-merge-stable', // merge stable into master 294 | 'shell:git-push-origin-master', // push changes to your fork 295 | 'shell:git-push-upstream-master' // push changes upstream 296 | ]); 297 | }); 298 | 299 | // Can't bail out when a prompt-confirm returns false, so need this hack 300 | // https://github.com/dylang/grunt-prompt/issues/4 301 | grunt.registerTask('prompt-release', ['prompt:release', 'prompt-release-check']); 302 | grunt.registerTask('prompt-release-check', '', function(type) { 303 | if(!grunt.config('release')) { 304 | grunt.fatal('Confirmation failed.'); 305 | } 306 | }); 307 | }; 308 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Brightcove, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This project is no longer maintained as Adobe Flash is being [End-of-lifed.](https://www.adobe.com/products/flashplayer/end-of-life.html) 2 | 3 | The lightweight Flash video player for Video.js. This allows the Video.js player's skins, plugins, and other features to work with both HTML5 and Flash. 4 | 5 | This project doesn't need to be used if you simply want to use the Flash tech in Video.js. 6 | 7 | - For Video.js 5.x and below, the Flash tech is part of the [Video.js core repository](https://github.com/videojs/video.js). 8 | - For Video.js 6.x and above, the Flash tech is in a [separate repository](https://github.com/videojs/videojs-flash). 9 | 10 | ## Installation 11 | 12 | 1. Install Node Packages. 13 | ```bash 14 | npm install 15 | ``` 16 | 2. Compile SWF. 17 | Development (places new SWF in /dist/): 18 | ```bash 19 | grunt mxmlc 20 | ``` 21 | Production/ Distribution (runs mxmlc task and copies SWF to dist/): 22 | ```bash 23 | grunt dist 24 | ``` 25 | 3. Run Connect Server. 26 | ```bash 27 | grunt connect:dev 28 | ``` 29 | 4. Open your browser at [http://localhost:8000/index.html](http://localhost:8000/index.html) to see a video play. You can keep using grunt to rebuild the Flash code. 30 | 31 | ## Releasing 32 | 33 | 1. Make sure that the following file is modified with these values: 34 | 35 | ``` 36 | node_modules/flex-sdk/lib/flex_sdk/frameworks/flex-config.xml 37 | ``` 38 | 39 | ```xml 40 | 41 | 10.3 42 | 43 | 44 | 12 45 | ``` 46 | 47 | 2. Run the commands: 48 | ```sh 49 | npm version {major,minor,patch} 50 | npm publish 51 | ``` 52 | The swf and changelog will be automatically built and added to the repo on version. 53 | 54 | ## Running Unit and Integration Tests 55 | 56 | ** Note - We want to drop all of this for grunt based / Karma testing. 57 | 58 | For unit tests, this project uses [FlexUnit](http://flexunit.org/). The unit tests can be found in [project root]/src/com/videojs/test/ 59 | 60 | For integration tests, this project uses [qunit](http://qunitjs.com/). The integration tests can be found in [project root]/test 61 | 62 | In order to run all of the tests, use the links at [http://localhost:8000/index.html](http://localhost:8000/index.html) 63 | 64 | There are very few tests. Adding to them is a fantastic and much appreciated way to contribute. 65 | -------------------------------------------------------------------------------- /contrib.json: -------------------------------------------------------------------------------- 1 | { 2 | "owner": "videojs", 3 | "project": "video-js-swf", 4 | "developmentBranch": "master", 5 | "releaseBranch": "stable", 6 | "remote": "origin" 7 | } 8 | -------------------------------------------------------------------------------- /dist/video-js.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videojs/video-js-swf/4b609118e090ec07682344ae421d18c92042d148/dist/video-js.swf -------------------------------------------------------------------------------- /dist/video-js.swf.old: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videojs/video-js-swf/4b609118e090ec07682344ae421d18c92042d148/dist/video-js.swf.old -------------------------------------------------------------------------------- /dist/video-js.swf.orig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videojs/video-js-swf/4b609118e090ec07682344ae421d18c92042d148/dist/video-js.swf.orig -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | Manual Tests
2 | Automated Tests (needs some work to use without local changes)
3 | Sandbox (all files in this directory will be ignored)
4 | -------------------------------------------------------------------------------- /libs/flexunit-4.1.0-8-as3_4.1.0.16076.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videojs/video-js-swf/4b609118e090ec07682344ae421d18c92042d148/libs/flexunit-4.1.0-8-as3_4.1.0.16076.swc -------------------------------------------------------------------------------- /libs/flexunit-4.1.0-8-flex_4.1.0.16076.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videojs/video-js-swf/4b609118e090ec07682344ae421d18c92042d148/libs/flexunit-4.1.0-8-flex_4.1.0.16076.swc -------------------------------------------------------------------------------- /libs/flexunit-aircilistener-4.1.0-8-4.1.0.16076.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videojs/video-js-swf/4b609118e090ec07682344ae421d18c92042d148/libs/flexunit-aircilistener-4.1.0-8-4.1.0.16076.swc -------------------------------------------------------------------------------- /libs/flexunit-cilistener-4.1.0-8-4.1.0.16076.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videojs/video-js-swf/4b609118e090ec07682344ae421d18c92042d148/libs/flexunit-cilistener-4.1.0-8-4.1.0.16076.swc -------------------------------------------------------------------------------- /libs/flexunit-flexcoverlistener-4.1.0-8-4.1.0.16076.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videojs/video-js-swf/4b609118e090ec07682344ae421d18c92042d148/libs/flexunit-flexcoverlistener-4.1.0-8-4.1.0.16076.swc -------------------------------------------------------------------------------- /libs/flexunit-uilistener-4.1.0-8-4.1.0.16076.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videojs/video-js-swf/4b609118e090ec07682344ae421d18c92042d148/libs/flexunit-uilistener-4.1.0-8-4.1.0.16076.swc -------------------------------------------------------------------------------- /libs/fluint-extensions-4.1.0-8-4.1.0.16076.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videojs/video-js-swf/4b609118e090ec07682344ae421d18c92042d148/libs/fluint-extensions-4.1.0-8-4.1.0.16076.swc -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "videojs-swf", 3 | "description": "The Flash-fallback video player for video.js (http://videojs.com)", 4 | "version": "5.4.2", 5 | "copyright": "Copyright 2014 Brightcove, Inc. https://github.com/videojs/video-js-swf/blob/master/LICENSE", 6 | "license": "Apache-2.0", 7 | "keywords": [ 8 | "flash", 9 | "video", 10 | "player" 11 | ], 12 | "homepage": "http://videojs.com", 13 | "author": "Brightcove", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/videojs/video-js-swf.git" 17 | }, 18 | "devDependencies": { 19 | "async": "~0.2.9", 20 | "chg": "^0.3.2", 21 | "flex-sdk": "4.6.0-0", 22 | "grunt": "~0.4.0", 23 | "grunt-bumpup": "~0.5.0", 24 | "grunt-cli": "~0.1.0", 25 | "grunt-connect": "~0.2.0", 26 | "grunt-contrib-jshint": "~0.4.3", 27 | "grunt-contrib-qunit": "~0.2.1", 28 | "grunt-contrib-watch": "~0.1.4", 29 | "grunt-npm": "~0.0.2", 30 | "grunt-prompt": "~0.1.2", 31 | "grunt-shell": "~0.6.1", 32 | "grunt-tagrelease": "~0.3.1", 33 | "qunitjs": "~1.12.0", 34 | "video.js": "^5.9.2" 35 | }, 36 | "scripts": { 37 | "version": "chg release -y && grunt dist && git add -f dist/ && git add CHANGELOG.md" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sandbox/videojs.html.example: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Video.js | HTML5 Video Player 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/AirTestRunner.mxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/VideoJS.as: -------------------------------------------------------------------------------- 1 | package{ 2 | 3 | import com.videojs.VideoJSApp; 4 | import com.videojs.events.VideoJSEvent; 5 | import com.videojs.structs.ExternalEventName; 6 | import com.videojs.structs.ExternalErrorEventName; 7 | import com.videojs.Base64; 8 | 9 | import flash.display.Sprite; 10 | import flash.display.StageAlign; 11 | import flash.display.StageScaleMode; 12 | import flash.events.Event; 13 | import flash.events.MouseEvent; 14 | import flash.events.TimerEvent; 15 | import flash.external.ExternalInterface; 16 | import flash.geom.Rectangle; 17 | import flash.system.Security; 18 | import flash.ui.ContextMenu; 19 | import flash.ui.ContextMenuItem; 20 | import flash.utils.ByteArray; 21 | import flash.utils.Timer; 22 | import flash.utils.setTimeout; 23 | 24 | [SWF(backgroundColor="#000000", frameRate="60", width="480", height="270")] 25 | public class VideoJS extends Sprite{ 26 | 27 | public const VERSION:String = CONFIG::version; 28 | 29 | private var _app:VideoJSApp; 30 | private var _stageSizeTimer:Timer; 31 | 32 | public function VideoJS(){ 33 | _stageSizeTimer = new Timer(250); 34 | _stageSizeTimer.addEventListener(TimerEvent.TIMER, onStageSizeTimerTick); 35 | addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); 36 | } 37 | 38 | private function init():void{ 39 | // Allow JS calls from other domains 40 | Security.allowDomain("*"); 41 | Security.allowInsecureDomain("*"); 42 | 43 | if(loaderInfo.hasOwnProperty("uncaughtErrorEvents")){ 44 | // we'll want to suppress ANY uncaught debug errors in production (for the sake of ux) 45 | // IEventDispatcher(loaderInfo["uncaughtErrorEvents"]).addEventListener("uncaughtError", onUncaughtError); 46 | } 47 | 48 | if(ExternalInterface.available){ 49 | registerExternalMethods(); 50 | } 51 | 52 | _app = new VideoJSApp(); 53 | addChild(_app); 54 | 55 | _app.model.stageRect = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight); 56 | 57 | // add content-menu version info 58 | 59 | var _ctxVersion:ContextMenuItem = new ContextMenuItem("VideoJS Flash Component v" + VERSION, false, false); 60 | var _ctxAbout:ContextMenuItem = new ContextMenuItem("Copyright © 2014 Brightcove, Inc.", false, false); 61 | var _ctxMenu:ContextMenu = new ContextMenu(); 62 | _ctxMenu.hideBuiltInItems(); 63 | _ctxMenu.customItems.push(_ctxVersion, _ctxAbout); 64 | this.contextMenu = _ctxMenu; 65 | 66 | } 67 | 68 | private function registerExternalMethods():void{ 69 | ExternalInterface.marshallExceptions = true; 70 | try{ 71 | ExternalInterface.addCallback("vjs_appendBuffer", onAppendBufferCalled); 72 | ExternalInterface.addCallback("vjs_appendChunkReady", onAppendChunkReadyCalled); 73 | ExternalInterface.addCallback("vjs_echo", onEchoCalled); 74 | ExternalInterface.addCallback("vjs_endOfStream", onEndOfStreamCalled); 75 | ExternalInterface.addCallback("vjs_abort", onAbortCalled); 76 | ExternalInterface.addCallback("vjs_discontinuity", onDiscontinuityCalled); 77 | 78 | ExternalInterface.addCallback("vjs_getProperty", onGetPropertyCalled); 79 | ExternalInterface.addCallback("vjs_setProperty", onSetPropertyCalled); 80 | ExternalInterface.addCallback("vjs_autoplay", onAutoplayCalled); 81 | ExternalInterface.addCallback("vjs_src", onSrcCalled); 82 | ExternalInterface.addCallback("vjs_load", onLoadCalled); 83 | ExternalInterface.addCallback("vjs_play", onPlayCalled); 84 | ExternalInterface.addCallback("vjs_pause", onPauseCalled); 85 | ExternalInterface.addCallback("vjs_resume", onResumeCalled); 86 | ExternalInterface.addCallback("vjs_stop", onStopCalled); 87 | 88 | // This callback should only be used when in data generation mode as it 89 | // will adjust the notion of current time without notifiying the player 90 | ExternalInterface.addCallback("vjs_adjustCurrentTime", onAdjustCurrentTimeCalled); 91 | } 92 | catch(e:SecurityError){ 93 | if (loaderInfo.parameters.debug != undefined && loaderInfo.parameters.debug == "true") { 94 | throw new SecurityError(e.message); 95 | } 96 | } 97 | catch(e:Error){ 98 | if (loaderInfo.parameters.debug != undefined && loaderInfo.parameters.debug == "true") { 99 | throw new Error(e.message); 100 | } 101 | } 102 | finally{} 103 | 104 | 105 | 106 | setTimeout(finish, 50); 107 | 108 | } 109 | 110 | private function finish():void{ 111 | 112 | if(loaderInfo.parameters.mode != undefined){ 113 | _app.model.mode = loaderInfo.parameters.mode; 114 | } 115 | 116 | // Hard coding these in for now until we can come up with a better solution for 5.0 to avoid XSS. 117 | _app.model.jsEventProxyName = 'videojs.Flash.onEvent'; 118 | _app.model.jsErrorEventProxyName = 'videojs.Flash.onError'; 119 | 120 | /*if(loaderInfo.parameters.eventProxyFunction != undefined){ 121 | _app.model.jsEventProxyName = loaderInfo.parameters.eventProxyFunction; 122 | } 123 | 124 | if(loaderInfo.parameters.errorEventProxyFunction != undefined){ 125 | _app.model.jsErrorEventProxyName = loaderInfo.parameters.errorEventProxyFunction; 126 | }*/ 127 | 128 | if(loaderInfo.parameters.autoplay != undefined && loaderInfo.parameters.autoplay == "true"){ 129 | _app.model.autoplay = true; 130 | } 131 | 132 | if(loaderInfo.parameters.preload != undefined && loaderInfo.parameters.preload != ""){ 133 | _app.model.preload = String(loaderInfo.parameters.preload); 134 | } 135 | 136 | if(loaderInfo.parameters.muted != undefined && loaderInfo.parameters.muted == "true"){ 137 | _app.model.muted = true; 138 | } 139 | 140 | if(loaderInfo.parameters.loop != undefined && loaderInfo.parameters.loop == "true"){ 141 | _app.model.loop = true; 142 | } 143 | 144 | if(loaderInfo.parameters.src != undefined && loaderInfo.parameters.src != ""){ 145 | if (isExternalMSObjectURL(loaderInfo.parameters.src)) { 146 | _app.model.srcFromFlashvars = null; 147 | openExternalMSObject(loaderInfo.parameters.src); 148 | } else { 149 | _app.model.srcFromFlashvars = String(loaderInfo.parameters.src); 150 | } 151 | } else{ 152 | if(loaderInfo.parameters.rtmpConnection != undefined && loaderInfo.parameters.rtmpConnection != ""){ 153 | _app.model.rtmpConnectionURL = loaderInfo.parameters.rtmpConnection; 154 | } 155 | 156 | if(loaderInfo.parameters.rtmpStream != undefined && loaderInfo.parameters.rtmpStream != ""){ 157 | _app.model.rtmpStream = loaderInfo.parameters.rtmpStream; 158 | } 159 | } 160 | 161 | // Hard coding this in for now until we can come up with a better solution for 5.0 to avoid XSS. 162 | ExternalInterface.call('videojs.Flash.onReady', ExternalInterface.objectID); 163 | 164 | /*if(loaderInfo.parameters.readyFunction != undefined){ 165 | try{ 166 | ExternalInterface.call(_app.model.cleanEIString(loaderInfo.parameters.readyFunction), ExternalInterface.objectID); 167 | } 168 | catch(e:Error){ 169 | if (loaderInfo.parameters.debug != undefined && loaderInfo.parameters.debug == "true") { 170 | throw new Error(e.message); 171 | } 172 | } 173 | }*/ 174 | } 175 | 176 | private function onAddedToStage(e:Event):void{ 177 | stage.addEventListener(MouseEvent.CLICK, onStageClick); 178 | stage.addEventListener(Event.RESIZE, onStageResize); 179 | stage.scaleMode = StageScaleMode.NO_SCALE; 180 | stage.align = StageAlign.TOP_LEFT; 181 | _stageSizeTimer.start(); 182 | } 183 | 184 | private function onStageSizeTimerTick(e:TimerEvent):void{ 185 | if(stage.stageWidth > 0 && stage.stageHeight > 0){ 186 | _stageSizeTimer.stop(); 187 | _stageSizeTimer.removeEventListener(TimerEvent.TIMER, onStageSizeTimerTick); 188 | init(); 189 | } 190 | } 191 | 192 | private function onStageResize(e:Event):void{ 193 | if(_app != null){ 194 | _app.model.stageRect = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight); 195 | _app.model.broadcastEvent(new VideoJSEvent(VideoJSEvent.STAGE_RESIZE, {})); 196 | } 197 | } 198 | 199 | private function onAppendBufferCalled(base64str:String):void{ 200 | var bytes:ByteArray = Base64.decode(base64str); 201 | // write the bytes to the provider 202 | _app.model.appendBuffer(bytes); 203 | } 204 | 205 | private function onAppendChunkReadyCalled(fnName:String):void{ 206 | var bytes:ByteArray = Base64.decode(ExternalInterface.call(fnName)); 207 | 208 | // write the bytes to the provider 209 | _app.model.appendBuffer(bytes); 210 | } 211 | 212 | private function onAdjustCurrentTimeCalled(pValue:Number):void { 213 | _app.model.adjustCurrentTime(pValue); 214 | } 215 | 216 | private function onEchoCalled(pResponse:* = null):*{ 217 | return pResponse; 218 | } 219 | 220 | private function onEndOfStreamCalled():*{ 221 | _app.model.endOfStream(); 222 | } 223 | 224 | private function onAbortCalled():*{ 225 | _app.model.abort(); 226 | } 227 | 228 | private function onDiscontinuityCalled():*{ 229 | _app.model.discontinuity(); 230 | } 231 | 232 | private function onGetPropertyCalled(pPropertyName:String = ""):*{ 233 | 234 | switch(pPropertyName){ 235 | case "mode": 236 | return _app.model.mode; 237 | case "autoplay": 238 | return _app.model.autoplay; 239 | case "loop": 240 | return _app.model.loop; 241 | case "preload": 242 | return _app.model.preload; 243 | break; 244 | case "metadata": 245 | return _app.model.metadata; 246 | break; 247 | case "duration": 248 | return _app.model.duration; 249 | break; 250 | case "eventProxyFunction": 251 | return _app.model.jsEventProxyName; 252 | break; 253 | case "errorEventProxyFunction": 254 | return _app.model.jsErrorEventProxyName; 255 | break; 256 | case "currentSrc": 257 | return _app.model.src; 258 | break; 259 | case "currentTime": 260 | return _app.model.time; 261 | break; 262 | case "time": 263 | return _app.model.time; 264 | break; 265 | case "initialTime": 266 | return 0; 267 | break; 268 | case "defaultPlaybackRate": 269 | return 1; 270 | break; 271 | case "ended": 272 | return _app.model.hasEnded; 273 | break; 274 | case "volume": 275 | return _app.model.volume; 276 | break; 277 | case "muted": 278 | return _app.model.muted; 279 | break; 280 | case "paused": 281 | return _app.model.paused; 282 | break; 283 | case "seeking": 284 | return _app.model.seeking; 285 | break; 286 | case "networkState": 287 | return _app.model.networkState; 288 | break; 289 | case "readyState": 290 | return _app.model.readyState; 291 | break; 292 | case "buffered": 293 | return _app.model.buffered; 294 | break; 295 | case "bufferedBytesStart": 296 | return 0; 297 | break; 298 | case "bufferedBytesEnd": 299 | return _app.model.bufferedBytesEnd; 300 | break; 301 | case "bytesTotal": 302 | return _app.model.bytesTotal; 303 | break; 304 | case "videoWidth": 305 | return _app.model.videoWidth; 306 | break; 307 | case "videoHeight": 308 | return _app.model.videoHeight; 309 | break; 310 | case "rtmpConnection": 311 | return _app.model.rtmpConnectionURL; 312 | break; 313 | case "rtmpStream": 314 | return _app.model.rtmpStream; 315 | break; 316 | case "getVideoPlaybackQuality": 317 | return _app.model.videoPlaybackQuality; 318 | break; 319 | } 320 | return null; 321 | } 322 | 323 | private function onSetPropertyCalled(pPropertyName:String = "", pValue:* = null):void{ 324 | switch(pPropertyName){ 325 | case "duration": 326 | _app.model.duration = Number(pValue); 327 | break; 328 | case "mode": 329 | _app.model.mode = String(pValue); 330 | break; 331 | case "loop": 332 | _app.model.loop = _app.model.humanToBoolean(pValue); 333 | break; 334 | case "background": 335 | _app.model.backgroundColor = _app.model.hexToNumber(String(pValue)); 336 | _app.model.backgroundAlpha = 1; 337 | break; 338 | case "eventProxyFunction": 339 | _app.model.jsEventProxyName = String(pValue); 340 | break; 341 | case "errorEventProxyFunction": 342 | _app.model.jsErrorEventProxyName = String(pValue); 343 | break; 344 | case "autoplay": 345 | _app.model.autoplay = _app.model.humanToBoolean(pValue); 346 | if (_app.model.autoplay) { 347 | _app.model.preload = "auto"; 348 | } 349 | break; 350 | case "preload": 351 | _app.model.preload = String(pValue); 352 | break; 353 | case "src": 354 | // same as when vjs_src() is called directly 355 | onSrcCalled(pValue); 356 | break; 357 | case "currentTime": 358 | _app.model.seekBySeconds(Number(pValue)); 359 | break; 360 | case "currentPercent": 361 | _app.model.seekByPercent(Number(pValue)); 362 | break; 363 | case "muted": 364 | _app.model.muted = _app.model.humanToBoolean(pValue); 365 | break; 366 | case "volume": 367 | _app.model.volume = Number(pValue); 368 | break; 369 | case "rtmpConnection": 370 | _app.model.rtmpConnectionURL = String(pValue); 371 | break; 372 | case "rtmpStream": 373 | _app.model.rtmpStream = String(pValue); 374 | break; 375 | default: 376 | _app.model.broadcastErrorEventExternally(ExternalErrorEventName.PROPERTY_NOT_FOUND, pPropertyName); 377 | break; 378 | } 379 | } 380 | 381 | private function onAutoplayCalled(pAutoplay:* = false):void{ 382 | _app.model.autoplay = _app.model.humanToBoolean(pAutoplay); 383 | } 384 | 385 | private function isExternalMSObjectURL(pSrc:*):Boolean{ 386 | return pSrc.indexOf('blob:vjs-media-source/') === 0; 387 | } 388 | 389 | private function openExternalMSObject(pSrc:*):void{ 390 | var cleanSrc:String 391 | if (/^blob:vjs-media-source\/\d+$/.test(pSrc)) { 392 | cleanSrc = pSrc; 393 | } else { 394 | cleanSrc = _app.model.cleanEIString(pSrc); 395 | } 396 | ExternalInterface.call('videojs.MediaSource.open', cleanSrc, ExternalInterface.objectID); 397 | } 398 | 399 | private function onSrcCalled(pSrc:* = ""):void{ 400 | // check if an external media source object will provide the video data 401 | if (isExternalMSObjectURL(pSrc)) { 402 | // null is passed to the netstream which enables appendBytes mode 403 | _app.model.src = null; 404 | // open the media source object for creating a source buffer 405 | // and provide a reference to this swf for passing data from the soure buffer 406 | openExternalMSObject(pSrc); 407 | 408 | // ExternalInterface.call('videojs.MediaSource.sourceBufferUrls["' + pSrc + '"]', ExternalInterface.objectID); 409 | } else { 410 | _app.model.src = String(pSrc); 411 | } 412 | } 413 | 414 | private function onLoadCalled():void{ 415 | _app.model.load(); 416 | } 417 | 418 | private function onPlayCalled():void{ 419 | _app.model.play(); 420 | } 421 | 422 | private function onPauseCalled():void{ 423 | _app.model.pause(); 424 | } 425 | 426 | private function onResumeCalled():void{ 427 | _app.model.resume(); 428 | } 429 | 430 | private function onStopCalled():void{ 431 | _app.model.stop(); 432 | } 433 | 434 | private function onUncaughtError(e:Event):void{ 435 | e.preventDefault(); 436 | } 437 | 438 | private function onStageClick(e:MouseEvent):void{ 439 | _app.model.broadcastEventExternally(ExternalEventName.ON_STAGE_CLICK); 440 | } 441 | 442 | } 443 | } 444 | -------------------------------------------------------------------------------- /src/com/videojs/Base64.as: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Jean-Philippe Auclair 3 | * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 4 | * Base64 library for ActionScript 3.0. 5 | * By: Jean-Philippe Auclair : http://jpauclair.net 6 | * Based on article: http://jpauclair.net/2010/01/09/base64-optimized-as3-lib/ 7 | * Benchmark: 8 | * This version: encode: 260ms decode: 255ms 9 | * Blog version: encode: 322ms decode: 694ms 10 | * as3Crypto encode: 6728ms decode: 4098ms 11 | * 12 | * Encode: com.sociodox.utils.Base64 is 25.8x faster than as3Crypto Base64 13 | * Decode: com.sociodox.utils.Base64 is 16x faster than as3Crypto Base64 14 | * 15 | * Optimize & Profile any Flash content with TheMiner ( http://www.sociodox.com/theminer ) 16 | */ 17 | package com.videojs { 18 | 19 | import flash.utils.ByteArray; 20 | 21 | public class Base64 { 22 | 23 | // TEMP BASE64 FOR TESTING 24 | // From: http://www.sociodox.com/base64.html 25 | private static const _decodeChars:Vector. = InitDecodeChar(); 26 | 27 | public static function decode(str:String):ByteArray 28 | { 29 | var c1:int; 30 | var c2:int; 31 | var c3:int; 32 | var c4:int; 33 | var i:int = 0; 34 | var len:int = str.length; 35 | 36 | var byteString:ByteArray = new ByteArray(); 37 | byteString.writeUTFBytes(str); 38 | var outPos:int = 0; 39 | while (i < len) 40 | { 41 | //c1 42 | c1 = _decodeChars[int(byteString[i++])]; 43 | if (c1 == -1) 44 | break; 45 | 46 | //c2 47 | c2 = _decodeChars[int(byteString[i++])]; 48 | if (c2 == -1) 49 | break; 50 | 51 | byteString[int(outPos++)] = (c1 << 2) | ((c2 & 0x30) >> 4); 52 | 53 | //c3 54 | c3 = byteString[int(i++)]; 55 | if (c3 == 61) 56 | { 57 | byteString.length = outPos 58 | return byteString; 59 | } 60 | 61 | c3 = _decodeChars[int(c3)]; 62 | if (c3 == -1) 63 | break; 64 | 65 | byteString[int(outPos++)] = ((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2); 66 | 67 | //c4 68 | c4 = byteString[int(i++)]; 69 | if (c4 == 61) 70 | { 71 | byteString.length = outPos 72 | return byteString; 73 | } 74 | 75 | c4 = _decodeChars[int(c4)]; 76 | if (c4 == -1) 77 | break; 78 | 79 | byteString[int(outPos++)] = ((c3 & 0x03) << 6) | c4; 80 | } 81 | byteString.length = outPos 82 | return byteString; 83 | } 84 | 85 | public static function InitDecodeChar():Vector. 86 | { 87 | 88 | var decodeChars:Vector. = new [ 89 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 90 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 92 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, 93 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 94 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 95 | -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 96 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, 97 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 98 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 99 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 100 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 101 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 102 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 103 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 104 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]; 105 | 106 | return decodeChars; 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /src/com/videojs/VideoJSApp.as: -------------------------------------------------------------------------------- 1 | package com.videojs{ 2 | 3 | import flash.display.Sprite; 4 | 5 | public class VideoJSApp extends Sprite{ 6 | 7 | private var _uiView:VideoJSView; 8 | private var _model:VideoJSModel; 9 | 10 | public function VideoJSApp(){ 11 | 12 | _model = VideoJSModel.getInstance() 13 | 14 | _uiView = new VideoJSView(); 15 | addChild(_uiView); 16 | 17 | } 18 | 19 | public function get model():VideoJSModel{ 20 | return _model; 21 | } 22 | 23 | } 24 | } -------------------------------------------------------------------------------- /src/com/videojs/VideoJSView.as: -------------------------------------------------------------------------------- 1 | package com.videojs{ 2 | 3 | import com.videojs.events.VideoJSEvent; 4 | import com.videojs.events.VideoPlaybackEvent; 5 | import com.videojs.structs.ExternalErrorEventName; 6 | 7 | import flash.display.Bitmap; 8 | import flash.display.Loader; 9 | import flash.display.Sprite; 10 | import flash.events.Event; 11 | import flash.events.IOErrorEvent; 12 | import flash.events.SecurityErrorEvent; 13 | import flash.external.ExternalInterface; 14 | import flash.geom.Rectangle; 15 | import flash.media.Video; 16 | import flash.net.URLRequest; 17 | import flash.system.LoaderContext; 18 | 19 | public class VideoJSView extends Sprite{ 20 | 21 | private var _uiVideo:Video; 22 | private var _uiBackground:Sprite; 23 | 24 | private var _model:VideoJSModel; 25 | 26 | public function VideoJSView(){ 27 | 28 | _model = VideoJSModel.getInstance(); 29 | _model.addEventListener(VideoJSEvent.BACKGROUND_COLOR_SET, onBackgroundColorSet); 30 | _model.addEventListener(VideoJSEvent.STAGE_RESIZE, onStageResize); 31 | _model.addEventListener(VideoPlaybackEvent.ON_META_DATA, onMetaData); 32 | _model.addEventListener(VideoPlaybackEvent.ON_VIDEO_DIMENSION_UPDATE, onDimensionUpdate); 33 | 34 | _uiBackground = new Sprite(); 35 | _uiBackground.graphics.beginFill(_model.backgroundColor, 1); 36 | _uiBackground.graphics.drawRect(0, 0, _model.stageRect.width, _model.stageRect.height); 37 | _uiBackground.graphics.endFill(); 38 | _uiBackground.alpha = _model.backgroundAlpha; 39 | addChild(_uiBackground); 40 | 41 | _uiVideo = new Video(); 42 | _uiVideo.width = _model.stageRect.width; 43 | _uiVideo.height = _model.stageRect.height; 44 | _uiVideo.smoothing = true; 45 | addChild(_uiVideo); 46 | 47 | _model.videoReference = _uiVideo; 48 | 49 | } 50 | 51 | 52 | private function sizeVideoObject():void{ 53 | 54 | var __targetWidth:int, __targetHeight:int; 55 | 56 | var __availableWidth:int = _model.stageRect.width; 57 | var __availableHeight:int = _model.stageRect.height; 58 | 59 | var __nativeWidth:int = 100; 60 | 61 | if(_model.metadata.width != undefined){ 62 | __nativeWidth = Number(_model.metadata.width); 63 | } 64 | 65 | if(_uiVideo.videoWidth != 0){ 66 | __nativeWidth = _uiVideo.videoWidth; 67 | } 68 | 69 | var __nativeHeight:int = 100; 70 | 71 | if(_model.metadata.width != undefined){ 72 | __nativeHeight = Number(_model.metadata.height); 73 | } 74 | 75 | if(_uiVideo.videoWidth != 0){ 76 | __nativeHeight = _uiVideo.videoHeight; 77 | } 78 | 79 | // first, size the whole thing down based on the available width 80 | __targetWidth = __availableWidth; 81 | __targetHeight = __targetWidth * (__nativeHeight / __nativeWidth); 82 | 83 | if(__targetHeight > __availableHeight){ 84 | __targetWidth = __targetWidth * (__availableHeight / __targetHeight); 85 | __targetHeight = __availableHeight; 86 | } 87 | 88 | _uiVideo.width = __targetWidth; 89 | _uiVideo.height = __targetHeight; 90 | 91 | _uiVideo.x = Math.round((_model.stageRect.width - _uiVideo.width) / 2); 92 | _uiVideo.y = Math.round((_model.stageRect.height - _uiVideo.height) / 2); 93 | 94 | 95 | } 96 | 97 | private function onBackgroundColorSet(e:VideoPlaybackEvent):void{ 98 | _uiBackground.graphics.clear(); 99 | _uiBackground.graphics.beginFill(_model.backgroundColor, 1); 100 | _uiBackground.graphics.drawRect(0, 0, _model.stageRect.width, _model.stageRect.height); 101 | _uiBackground.graphics.endFill(); 102 | } 103 | 104 | private function onStageResize(e:VideoJSEvent):void{ 105 | 106 | _uiBackground.graphics.clear(); 107 | _uiBackground.graphics.beginFill(_model.backgroundColor, 1); 108 | _uiBackground.graphics.drawRect(0, 0, _model.stageRect.width, _model.stageRect.height); 109 | _uiBackground.graphics.endFill(); 110 | sizeVideoObject(); 111 | } 112 | 113 | private function onMetaData(e:VideoPlaybackEvent):void{ 114 | sizeVideoObject(); 115 | } 116 | 117 | private function onDimensionUpdate(e:VideoPlaybackEvent):void{ 118 | sizeVideoObject(); 119 | } 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/com/videojs/events/VideoErrorEvent.as: -------------------------------------------------------------------------------- 1 | package com.videojs.events{ 2 | 3 | import flash.events.Event; 4 | 5 | public class VideoErrorEvent extends Event{ 6 | 7 | public static const SRC_MISSING:String = "VideoPlaybackEvent.SRC_MISSING"; 8 | 9 | // a flexible container object for whatever data needs to be attached to any of these events 10 | private var _data:Object; 11 | 12 | public function VideoErrorEvent(pType:String, pData:Object = null){ 13 | super(pType, true, false); 14 | _data = pData; 15 | } 16 | 17 | public function get data():Object { 18 | return _data; 19 | } 20 | 21 | } 22 | } -------------------------------------------------------------------------------- /src/com/videojs/events/VideoJSEvent.as: -------------------------------------------------------------------------------- 1 | package com.videojs.events{ 2 | 3 | import flash.events.Event; 4 | 5 | public class VideoJSEvent extends Event{ 6 | 7 | public static const STAGE_RESIZE:String = "VideoJSEvent.STAGE_RESIZE"; 8 | public static const BACKGROUND_COLOR_SET:String = "VideoJSEvent.BACKGROUND_COLOR_SET"; 9 | 10 | // a flexible container object for whatever data needs to be attached to any of these events 11 | private var _data:Object; 12 | 13 | public function VideoJSEvent(pType:String, pData:Object = null){ 14 | super(pType, true, false); 15 | _data = pData; 16 | } 17 | 18 | public function get data():Object { 19 | return _data; 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/com/videojs/events/VideoPlaybackEvent.as: -------------------------------------------------------------------------------- 1 | package com.videojs.events{ 2 | 3 | import flash.events.Event; 4 | 5 | public class VideoPlaybackEvent extends Event{ 6 | 7 | public static const ON_CUE_POINT:String = "VideoPlaybackEvent.ON_CUE_POINT"; 8 | public static const ON_META_DATA:String = "VideoPlaybackEvent.ON_META_DATA"; 9 | public static const ON_XMP_DATA:String = "VideoPlaybackEvent.ON_XMP_DATA"; 10 | public static const ON_NETSTREAM_STATUS:String = "VideoPlaybackEvent.ON_NETSTREAM_STATUS"; 11 | public static const ON_NETCONNECTION_STATUS:String = "VideoPlaybackEvent.ON_NETCONNECTION_STATUS"; 12 | public static const ON_STREAM_READY:String = "VideoPlaybackEvent.ON_STREAM_READY"; 13 | public static const ON_STREAM_NOT_READY:String = "VideoPlaybackEvent.ON_STREAM_NOT_READY"; 14 | public static const ON_STREAM_START:String = "VideoPlaybackEvent.ON_STREAM_START"; 15 | public static const ON_STREAM_CLOSE:String = "VideoPlaybackEvent.ON_STREAM_CLOSE"; 16 | public static const ON_STREAM_METRICS_UPDATE:String = "VideoPlaybackEvent.ON_STREAM_METRICS_UPDATE"; 17 | public static const ON_STREAM_PAUSE:String = "VideoPlaybackEvent.ON_STREAM_PAUSE"; 18 | public static const ON_STREAM_RESUME:String = "VideoPlaybackEvent.ON_STREAM_RESUME"; 19 | public static const ON_STREAM_SEEK_COMPLETE:String = "VideoPlaybackEvent.ON_STREAM_SEEK_COMPLETE"; 20 | public static const ON_STREAM_REBUFFER_START:String = "VideoPlaybackEvent.ON_STREAM_REBUFFER_START"; 21 | public static const ON_STREAM_REBUFFER_END:String = "VideoPlaybackEvent.ON_STREAM_REBUFFER_END"; 22 | public static const ON_ERROR:String = "VideoPlaybackEvent.ON_ERROR"; 23 | public static const ON_UPDATE:String = "VideoPlaybackEvent.ON_UPDATE"; 24 | public static const ON_VIDEO_DIMENSION_UPDATE:String = "VideoPlaybackEvent.ON_VIDEO_DIMENSION_UPDATE"; 25 | public static const ON_TEXT_DATA:String = "VideoPlaybackEvent.ON_TEXT_DATA"; 26 | 27 | // a flexible container object for whatever data needs to be attached to any of these events 28 | private var _data:Object; 29 | 30 | public function VideoPlaybackEvent(pType:String, pData:Object = null){ 31 | super(pType, true, false); 32 | _data = pData; 33 | } 34 | 35 | public function get data():Object { 36 | return _data; 37 | } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/com/videojs/providers/HTTPAudioProvider.as: -------------------------------------------------------------------------------- 1 | package com.videojs.providers{ 2 | 3 | import com.videojs.VideoJSModel; 4 | import com.videojs.structs.ExternalErrorEventName; 5 | import com.videojs.structs.ExternalEventName; 6 | 7 | import flash.events.Event; 8 | import flash.events.IOErrorEvent; 9 | import flash.events.ProgressEvent; 10 | import flash.events.TimerEvent; 11 | import flash.external.ExternalInterface; 12 | import flash.media.Sound; 13 | import flash.media.SoundChannel; 14 | import flash.media.Video; 15 | import flash.net.URLRequest; 16 | import flash.utils.ByteArray; 17 | import flash.utils.Timer; 18 | import flash.utils.getTimer; 19 | 20 | 21 | public class HTTPAudioProvider implements IProvider{ 22 | 23 | private var _throughputTimer:Timer; 24 | private var _currentThroughput:int = 0; // in B/sec 25 | private var _loadStartTimestamp:int; 26 | private var _loadStarted:Boolean = false; 27 | private var _loadCompleted:Boolean = false; 28 | private var _loadErrored:Boolean = false; 29 | private var _isBuffering:Boolean = false; 30 | private var _src:Object; 31 | private var _metadata:Object; 32 | private var _loop:Boolean = false; 33 | private var _preloadInitiated:Boolean = false; 34 | 35 | private var _sound:Sound; 36 | private var _soundChannel:SoundChannel; 37 | private var _audioPlaybackStarted:Boolean = false; 38 | private var _audioPlaybackStopped:Boolean = false; 39 | private var _audioPlaybackPaused:Boolean = false; 40 | private var _audioIsSeeking:Boolean = false; 41 | private var _audioPlaybackHasEnded:Boolean = false; 42 | private var _audioBytesLoaded:int = 0; 43 | private var _audioBytesTotal:int = 0; 44 | private var _audioDuration:Number = 0; 45 | private var _audioPausePoint:Number = 0; 46 | private var _estimatedDurations:int = 0; 47 | private var _canPlayThroughDispatched:Boolean = false; 48 | 49 | private var _model:VideoJSModel; 50 | 51 | public function HTTPAudioProvider(){ 52 | _model = VideoJSModel.getInstance(); 53 | _metadata = {}; 54 | _throughputTimer = new Timer(250, 0); 55 | _throughputTimer.addEventListener(TimerEvent.TIMER, onThroughputTimerTick); 56 | } 57 | 58 | public function get loop():Boolean{ 59 | return _loop; 60 | } 61 | 62 | public function set loop(pLoop:Boolean):void{ 63 | _loop = pLoop; 64 | } 65 | 66 | public function get time():Number{ 67 | if(_audioPlaybackStarted){ 68 | if(_soundChannel != null){ 69 | return _soundChannel.position / 1000; 70 | } 71 | else{ 72 | return 0; 73 | } 74 | } 75 | else{ 76 | return 0; 77 | } 78 | } 79 | 80 | public function get duration():Number{ 81 | return _audioDuration / 1000; 82 | } 83 | 84 | public function get readyState():int{ 85 | if(_canPlayThroughDispatched){ 86 | return 4; 87 | } 88 | else if(_audioPlaybackStarted){ 89 | return 3; 90 | } 91 | else if(_loadStarted){ 92 | return 2; 93 | } 94 | else if(_estimatedDurations >= 5){ 95 | return 1; 96 | } 97 | else{ 98 | return 0; 99 | } 100 | } 101 | 102 | public function get networkState():int{ 103 | if(_loadErrored){ 104 | return 3; 105 | } 106 | else if(_loadStarted){ 107 | return 2; 108 | } 109 | else{ 110 | return 1; 111 | } 112 | } 113 | 114 | public function appendBuffer(bytes:ByteArray):void{ 115 | throw "HTTPAudioProvider does not support appendBuffer"; 116 | } 117 | 118 | public function endOfStream():void{ 119 | throw "HTTPAudioProvider does not support endOfStream"; 120 | } 121 | 122 | public function abort():void{ 123 | throw "HTTPAudioProvider does not support abort"; 124 | } 125 | 126 | public function discontinuity():void{ 127 | throw "HTTPAudioProvider does not support discontinuities"; 128 | } 129 | 130 | public function get buffered():Array{ 131 | if(duration > 0){ 132 | return [[0, (bytesLoaded / bytesTotal) * duration]]; 133 | } 134 | return []; 135 | } 136 | 137 | public function get bufferedBytesEnd():int{ 138 | return _audioBytesLoaded; 139 | } 140 | 141 | public function get bytesLoaded():int{ 142 | return _audioBytesLoaded; 143 | } 144 | 145 | public function get bytesTotal():int{ 146 | return _audioBytesTotal; 147 | } 148 | 149 | public function get playing():Boolean{ 150 | return _audioPlaybackStarted; 151 | } 152 | 153 | public function get paused():Boolean{ 154 | return _audioPlaybackPaused; 155 | } 156 | 157 | public function get ended():Boolean{ 158 | return _audioPlaybackHasEnded; 159 | } 160 | 161 | public function get seeking():Boolean{ 162 | return _audioIsSeeking; 163 | } 164 | 165 | public function get usesNetStream():Boolean{ 166 | return false; 167 | } 168 | 169 | public function get metadata():Object{ 170 | return _metadata; 171 | } 172 | 173 | public function get videoPlaybackQuality():Object{ 174 | // only meant for video 175 | return {}; 176 | } 177 | 178 | public function get srcAsString():String{ 179 | if(_src != null && _src.path != undefined){ 180 | return _src.path; 181 | } 182 | return ""; 183 | } 184 | 185 | public function set src(pSrc:Object):void{ 186 | _src = pSrc; 187 | } 188 | 189 | public function init(pSrc:Object, pAutoplay:Boolean):void{ 190 | _src = pSrc; 191 | _loadErrored = false; 192 | _loadStarted = false; 193 | _loadCompleted = false; 194 | if(pAutoplay){ 195 | play(); 196 | } 197 | } 198 | 199 | public function load():void{ 200 | 201 | _preloadInitiated = true; 202 | 203 | if(_src != ""){ 204 | 205 | // if we're already playing 206 | if(_audioPlaybackStarted){ 207 | _soundChannel.stop(); 208 | _soundChannel = null; 209 | _throughputTimer.stop(); 210 | _throughputTimer.reset(); 211 | } 212 | else{ 213 | if(_sound == null){ 214 | _sound = new Sound(); 215 | _sound.addEventListener(IOErrorEvent.IO_ERROR, onSoundLoadError); 216 | _sound.addEventListener(ProgressEvent.PROGRESS, onSoundProgress); 217 | _sound.addEventListener(Event.COMPLETE, onSoundLoadComplete); 218 | _sound.addEventListener(Event.OPEN, onSoundOpen); 219 | _sound.addEventListener(Event.ID3, onID3Loaded); 220 | } 221 | } 222 | 223 | // load the asset 224 | _audioPlaybackStarted = false; 225 | _audioPlaybackStopped = false; 226 | _audioPlaybackPaused = false; 227 | _audioPlaybackHasEnded = false; 228 | _audioBytesLoaded = 0; 229 | _audioBytesTotal = 0; 230 | _audioDuration = 0; 231 | _audioPausePoint = 0; 232 | _estimatedDurations = 0; 233 | _canPlayThroughDispatched = false; 234 | _loadErrored = false; 235 | var __request:URLRequest = new URLRequest(_src.path); 236 | try{ 237 | _sound.load(__request); 238 | } 239 | catch(e:Error){ 240 | _model.broadcastErrorEventExternally("audioloaderror"); 241 | } 242 | _model.broadcastEventExternally(ExternalEventName.ON_LOAD_START); 243 | _model.broadcastEventExternally(ExternalEventName.ON_BUFFER_EMPTY); 244 | } 245 | } 246 | 247 | public function play():void{ 248 | 249 | if(_src != ""){ 250 | 251 | if(_preloadInitiated){ 252 | _preloadInitiated = false; 253 | _soundChannel = _sound.play(); 254 | _soundChannel.addEventListener(Event.SOUND_COMPLETE, onSoundPlayComplete); 255 | } 256 | else{ 257 | // if we're already playing 258 | if(_audioPlaybackStarted){ 259 | // if we're paused 260 | if(_audioPlaybackPaused){ 261 | resume(); 262 | } 263 | else{ 264 | _soundChannel.stop(); 265 | _soundChannel = null; 266 | _throughputTimer.stop(); 267 | _throughputTimer.reset(); 268 | } 269 | } 270 | else{ 271 | _sound = new Sound(); 272 | _sound.addEventListener(IOErrorEvent.IO_ERROR, onSoundLoadError); 273 | _sound.addEventListener(ProgressEvent.PROGRESS, onSoundProgress); 274 | _sound.addEventListener(Event.COMPLETE, onSoundLoadComplete); 275 | _sound.addEventListener(Event.OPEN, onSoundOpen); 276 | _sound.addEventListener(Event.ID3, onID3Loaded); 277 | } 278 | 279 | // play the asset 280 | _audioPlaybackStarted = false; 281 | _audioPlaybackStopped = false; 282 | _audioPlaybackPaused = false; 283 | _audioPlaybackHasEnded = false; 284 | _audioBytesLoaded = 0; 285 | _audioBytesTotal = 0; 286 | _audioDuration = 0; 287 | _audioPausePoint = 0; 288 | _estimatedDurations = 0; 289 | _canPlayThroughDispatched = false; 290 | _loadErrored = false; 291 | var __request:URLRequest = new URLRequest(_src.path); 292 | try{ 293 | _sound.load(__request); 294 | } 295 | catch(e:Error){ 296 | _model.broadcastErrorEventExternally("audioloaderror"); 297 | } 298 | _soundChannel = _sound.play(); 299 | _soundChannel.addEventListener(Event.SOUND_COMPLETE, onSoundPlayComplete); 300 | _model.broadcastEventExternally(ExternalEventName.ON_LOAD_START); 301 | _model.broadcastEventExternally(ExternalEventName.ON_BUFFER_EMPTY); 302 | } 303 | } 304 | } 305 | 306 | public function pause():void{ 307 | if(_audioPlaybackStarted){ 308 | _audioPausePoint = _soundChannel.position; 309 | _soundChannel.stop(); 310 | _audioPlaybackPaused = true; 311 | _model.broadcastEventExternally(ExternalEventName.ON_PAUSE); 312 | } 313 | } 314 | 315 | public function resume():void{ 316 | if(_audioPlaybackStarted && _audioPlaybackPaused){ 317 | _soundChannel = _sound.play(_audioPausePoint); 318 | _audioPlaybackPaused = false; 319 | _model.broadcastEventExternally(ExternalEventName.ON_RESUME); 320 | _model.broadcastEventExternally(ExternalEventName.ON_START); 321 | } 322 | } 323 | 324 | public function seekBySeconds(pTime:Number):void{ 325 | if(_audioDuration > 0){ 326 | _soundChannel.stop(); 327 | _soundChannel = _sound.play(int(pTime * 1000)); 328 | _audioPlaybackStarted = true; 329 | _audioPlaybackHasEnded = false; 330 | _audioPlaybackPaused = false; 331 | _model.broadcastEventExternally(ExternalEventName.ON_SEEK_COMPLETE); 332 | } 333 | } 334 | 335 | public function adjustCurrentTime(pValue:Number):void { 336 | // no-op 337 | } 338 | 339 | public function seekByPercent(pPercent:Number):void{ 340 | if(_audioPlaybackStarted && _audioDuration > 0){ 341 | _soundChannel.stop(); 342 | _soundChannel = _sound.play(pPercent * _audioDuration); 343 | _audioPlaybackStarted = true; 344 | _audioPlaybackHasEnded = false; 345 | _audioPlaybackPaused = false; 346 | _model.broadcastEventExternally(ExternalEventName.ON_SEEK_COMPLETE); 347 | } 348 | } 349 | 350 | public function stop():void{ 351 | if(_audioPlaybackStarted){ 352 | _soundChannel.stop(); 353 | _audioPlaybackStarted = false; 354 | _audioPlaybackStopped = true; 355 | _audioPlaybackPaused = false; 356 | } 357 | } 358 | 359 | public function attachVideo(pVideo:Video):void{} 360 | 361 | public function die():void 362 | { 363 | if(_soundChannel) 364 | { 365 | try{ 366 | stop(); 367 | _soundChannel = null; 368 | } catch( err:Error ) { 369 | 370 | } 371 | } 372 | 373 | if(_throughputTimer) 374 | { 375 | try { 376 | _throughputTimer.stop(); 377 | _throughputTimer = null; 378 | } catch( err: Error ) { 379 | 380 | } 381 | } 382 | } 383 | 384 | private function doLoadCalculations():void{ 385 | // if the load is finished 386 | if(_sound.bytesLoaded == _sound.bytesTotal){ 387 | _loadCompleted = true; 388 | _audioDuration = _sound.length; 389 | _throughputTimer.stop(); 390 | _throughputTimer.reset(); 391 | _model.broadcastEventExternally(ExternalEventName.ON_DURATION_CHANGE, _audioDuration); 392 | _canPlayThroughDispatched = true; 393 | _model.broadcastEventExternally(ExternalEventName.ON_CAN_PLAY_THROUGH); 394 | } 395 | else{ 396 | var __percentLoaded:Number = _sound.bytesLoaded / _sound.bytesTotal; 397 | _audioDuration = _sound.length * (1 / __percentLoaded); 398 | _estimatedDurations++; 399 | // once we have 5 measurements 400 | if(_estimatedDurations == 5){ 401 | _model.broadcastEventExternally(ExternalEventName.ON_DURATION_CHANGE, _audioDuration); 402 | } 403 | else if(_estimatedDurations > 5){ 404 | var __throughput:Number = _sound.bytesLoaded / ((getTimer() - _loadStartTimestamp) / 1000); 405 | var __timeToLoad:Number = (_sound.bytesTotal - _sound.bytesLoaded) / __throughput; 406 | if(!_canPlayThroughDispatched && __timeToLoad < _audioDuration){ 407 | _canPlayThroughDispatched = true; 408 | _model.broadcastEventExternally(ExternalEventName.ON_CAN_PLAY_THROUGH); 409 | } 410 | } 411 | } 412 | } 413 | 414 | private function onThroughputTimerTick(e:TimerEvent):void{ 415 | doLoadCalculations(); 416 | } 417 | 418 | private function onSoundProgress(e:ProgressEvent):void{ 419 | _audioBytesLoaded = e.bytesLoaded; 420 | _audioBytesTotal = e.bytesTotal; 421 | _audioDuration = _sound.length 422 | } 423 | 424 | private function onSoundOpen(e:Event):void{ 425 | _loadStartTimestamp = getTimer(); 426 | _throughputTimer.start(); 427 | _audioPlaybackStarted = true; 428 | _model.broadcastEventExternally(ExternalEventName.ON_START); 429 | } 430 | 431 | private function onSoundLoadComplete(e:Event):void{ 432 | _throughputTimer.stop(); 433 | _throughputTimer.reset(); 434 | doLoadCalculations(); 435 | } 436 | 437 | private function onSoundPlayComplete(e:Event):void{ 438 | if(!_loop){ 439 | _audioPlaybackStarted = false; 440 | _audioPlaybackHasEnded = true; 441 | _model.broadcastEventExternally(ExternalEventName.ON_PLAYBACK_COMPLETE); 442 | } 443 | else{ 444 | // if we know the duration 445 | if(_audioDuration > 0){ 446 | _soundChannel.stop(); 447 | _soundChannel = _sound.play(0); 448 | _audioPlaybackStarted = true; 449 | } 450 | } 451 | } 452 | 453 | private function onSoundLoadError(e:IOErrorEvent):void{ 454 | _loadErrored = true; 455 | _model.broadcastErrorEventExternally(ExternalErrorEventName.SRC_404); 456 | } 457 | 458 | private function onID3Loaded(event:Event):void{ 459 | _metadata = { 460 | id3:_sound.id3 461 | } 462 | _model.broadcastEventExternally(ExternalEventName.ON_METADATA, _metadata); 463 | } 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /src/com/videojs/providers/IProvider.as: -------------------------------------------------------------------------------- 1 | package com.videojs.providers{ 2 | 3 | import flash.media.Video; 4 | import flash.utils.ByteArray; 5 | 6 | public interface IProvider{ 7 | 8 | /** 9 | * Should return a value that indicates whether or not looping is enabled. 10 | */ 11 | function get loop():Boolean; 12 | 13 | /** 14 | * See above. 15 | */ 16 | function set loop(pLoop:Boolean):void; 17 | 18 | /** 19 | * Should return a value that indicates the current playhead position, in seconds. 20 | */ 21 | function get time():Number; 22 | 23 | /** 24 | * Should return a value that indicates the current asset's duration, in seconds. 25 | */ 26 | function get duration():Number; 27 | 28 | /** 29 | * Appends the segment data in a ByteArray to the source buffer. 30 | * @param bytes the ByteArray of data to append. 31 | */ 32 | function appendBuffer(bytes:ByteArray):void; 33 | 34 | /** 35 | * Indicates that no further bytes will appended to the source 36 | * buffer. After this method has been called, reaching the end 37 | * of buffered input is equivalent to the end of the media. 38 | */ 39 | function endOfStream():void; 40 | 41 | /** 42 | * Aborts any data currently in the buffer and resets the decoder. 43 | * @see https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#widl-SourceBuffer-abort-void 44 | */ 45 | function abort():void; 46 | 47 | /** 48 | * Indicates the next bytes of content will have timestamp 49 | * values that are not contiguous with the current playback 50 | * timeline. 51 | */ 52 | function discontinuity():void; 53 | 54 | /** 55 | * Should return an interger that reflects the closest parallel to 56 | * HTMLMediaElement's readyState property, as described here: 57 | * https://developer.mozilla.org/en/DOM/HTMLMediaElement 58 | */ 59 | function get readyState():int; 60 | 61 | /** 62 | * Should return an interger that reflects the closest parallel to 63 | * HTMLMediaElement's networkState property, as described here: 64 | * https://developer.mozilla.org/en/DOM/HTMLMediaElement 65 | */ 66 | function get networkState():int; 67 | 68 | /** 69 | * Should return an array of normalized time ranges currently 70 | * buffered of the media, in seconds. 71 | */ 72 | function get buffered():Array; 73 | 74 | /** 75 | * Should return the number of bytes that have been loaded thus far, or 0 if 76 | * this value is unknown or unable to be calculated (due to streaming, bitrate switching, etc) 77 | */ 78 | function get bufferedBytesEnd():int; 79 | 80 | /** 81 | * Should return the number of bytes that have been loaded thus far, or 0 if 82 | * this value is unknown or unable to be calculated (due to streaming, bitrate switching, etc) 83 | */ 84 | function get bytesLoaded():int; 85 | 86 | /** 87 | * Should return the total bytes of the current asset, or 0 if this value is 88 | * unknown or unable to be determined (due to streaming, bitrate switching, etc) 89 | */ 90 | function get bytesTotal():int; 91 | 92 | /** 93 | * Should return a boolean value that indicates whether or not the current media 94 | * asset is playing. 95 | */ 96 | function get playing():Boolean; 97 | 98 | /** 99 | * Should return a boolean value that indicates whether or not the current media 100 | * asset is paused. 101 | */ 102 | function get paused():Boolean; 103 | 104 | /** 105 | * Should return a boolean value that indicates whether or not the current media 106 | * asset has ended. This value should default to false, and be reset with every seek request within 107 | * the same asset. 108 | */ 109 | function get ended():Boolean; 110 | 111 | /** 112 | * Should return a boolean value that indicates whether or not the current media 113 | * asset is in the process of seeking to a new time point. 114 | */ 115 | function get seeking():Boolean; 116 | 117 | /** 118 | * Should return a boolean value that indicates whether or not this provider uses the NetStream class. 119 | */ 120 | function get usesNetStream():Boolean; 121 | 122 | /** 123 | * Should return an object that contains metadata properties, or an empty object if metadata doesn't exist. 124 | */ 125 | function get metadata():Object; 126 | 127 | /** 128 | * Should return an object that contains playback quality details, following the 129 | * form of https://w3c.github.io/media-source/#VideoPlaybackQuality 130 | */ 131 | function get videoPlaybackQuality():Object; 132 | 133 | /** 134 | * Should return the most reasonable string representation of the current assets source location. 135 | */ 136 | function get srcAsString():String; 137 | 138 | /** 139 | * Should contain an object that enables the provider to play whatever media it's designed to play. 140 | * Compare the difference in implementation between HTTPVideoProvider and RTMPVideoProvider to see 141 | * one example of how this object can be used. 142 | */ 143 | function set src(pSrc:Object):void; 144 | 145 | /** 146 | * Should return the most reasonable string representation of the current assets source location. 147 | */ 148 | function init(pSrc:Object, pAutoplay:Boolean):void; 149 | 150 | /** 151 | * Called when the media asset should be preloaded, but not played. 152 | */ 153 | function load():void; 154 | 155 | /** 156 | * Called when the media asset should be played immediately. 157 | */ 158 | function play():void; 159 | 160 | /** 161 | * Called when the media asset should be paused. 162 | */ 163 | function pause():void; 164 | 165 | /** 166 | * Called when the media asset should be resumed from a paused state. 167 | */ 168 | function resume():void; 169 | 170 | /** 171 | * Called when current time needs to be adjusted slightly without seeking 172 | */ 173 | function adjustCurrentTime(pValue:Number):void; 174 | 175 | /** 176 | * Called when the media asset needs to seek to a new time point. 177 | */ 178 | function seekBySeconds(pTime:Number):void; 179 | 180 | /** 181 | * Called when the media asset needs to seek to a percentage of its total duration. 182 | */ 183 | function seekByPercent(pPercent:Number):void; 184 | 185 | /** 186 | * Called when the media asset needs to stop. 187 | */ 188 | function stop():void; 189 | 190 | /** 191 | * For providers that employ an instance of NetStream, this method is used to connect that NetStream 192 | * with an external Video instance without exposing it. 193 | */ 194 | function attachVideo(pVideo:Video):void; 195 | 196 | /** 197 | * Called when the provider is about to be disposed of. 198 | */ 199 | function die():void; 200 | 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/com/videojs/structs/ExternalErrorEventName.as: -------------------------------------------------------------------------------- 1 | package com.videojs.structs{ 2 | 3 | public class ExternalErrorEventName{ 4 | 5 | public static const SRC_NOT_SET:String = "srcnotset"; 6 | public static const SRC_404:String = "srcnotfound"; 7 | public static const RTMP_CONNECT_FAILURE:String = "rtmpconnectfailure"; 8 | public static const PROPERTY_NOT_FOUND:String = "propertynotfound"; 9 | public static const UNSUPPORTED_MODE:String = "unsupportedmode"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/com/videojs/structs/ExternalEventName.as: -------------------------------------------------------------------------------- 1 | package com.videojs.structs{ 2 | 3 | public class ExternalEventName{ 4 | 5 | public static const ON_SRC_CHANGE:String = "onsrcchange"; 6 | public static const ON_LOAD_START:String = "loadstart"; 7 | public static const ON_START:String = "playing"; 8 | public static const ON_PAUSE:String = "pause"; 9 | public static const ON_RESUME:String = "play"; 10 | public static const ON_SEEK_START:String = "seeking"; 11 | public static const ON_SEEK_COMPLETE:String = "seeked"; 12 | public static const ON_BUFFER_FULL:String = "loadeddata"; 13 | public static const ON_BUFFER_EMPTY:String = "waiting"; 14 | public static const ON_BUFFER_FLUSH:String = "emptied"; 15 | public static const ON_PLAYBACK_COMPLETE:String = "ended"; 16 | public static const ON_METADATA:String = "loadedmetadata"; 17 | public static const ON_DURATION_CHANGE:String = "durationchange"; 18 | public static const ON_CAN_PLAY:String = "canplay"; 19 | public static const ON_CAN_PLAY_THROUGH:String = "canplaythrough"; 20 | public static const ON_VOLUME_CHANGE:String = "volumechange"; 21 | 22 | public static const ON_RTMP_CONNECT_SUCCESS:String = "rtmpconnected"; 23 | public static const ON_RTMP_RETRY:String = "rtmpretry"; 24 | public static const ON_STAGE_CLICK:String = "stageclick"; 25 | 26 | public static const ON_TEXT_DATA:String = "textdata"; 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/com/videojs/structs/PlaybackType.as: -------------------------------------------------------------------------------- 1 | package com.videojs.structs{ 2 | 3 | public class PlaybackType{ 4 | 5 | public static const HTTP:String = "PlaybackType.HTTP"; 6 | public static const RTMP:String = "PlaybackType.RTMP"; 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /src/com/videojs/structs/PlayerMode.as: -------------------------------------------------------------------------------- 1 | package com.videojs.structs{ 2 | 3 | public class PlayerMode{ 4 | 5 | public static const VIDEO:String = "video"; 6 | public static const AUDIO:String = "audio"; 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /src/com/videojs/test/VideoJSModelTest.as: -------------------------------------------------------------------------------- 1 | package com.videojs.test 2 | { 3 | import org.flexunit.Assert; 4 | import com.videojs.VideoJSModel; 5 | 6 | public class VideoJSModelTest 7 | { 8 | [Test] 9 | public function test_backgroundColor():void 10 | { 11 | VideoJSModel.getInstance().backgroundColor = -1; 12 | Assert.assertEquals(0, VideoJSModel.getInstance().backgroundColor); 13 | 14 | VideoJSModel.getInstance().backgroundColor = 5; 15 | Assert.assertEquals(5, VideoJSModel.getInstance().backgroundColor); 16 | 17 | VideoJSModel.getInstance().backgroundColor = 0; 18 | Assert.assertEquals(0, VideoJSModel.getInstance().backgroundColor); 19 | } 20 | 21 | [Test] 22 | public function test_volume():void 23 | { 24 | VideoJSModel.getInstance().volume = -1; 25 | Assert.assertEquals(1, VideoJSModel.getInstance().volume); 26 | 27 | VideoJSModel.getInstance().volume = 2; 28 | Assert.assertEquals(1, VideoJSModel.getInstance().volume); 29 | 30 | VideoJSModel.getInstance().volume = 0.5; 31 | Assert.assertEquals(0.5, VideoJSModel.getInstance().volume); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/com/videojs/test/suite/VideoJSTestSuite.as: -------------------------------------------------------------------------------- 1 | package com.videojs.test.suite 2 | { 3 | import com.videojs.test.VideoJSModelTest; 4 | 5 | [Suite] 6 | [RunWith("org.flexunit.runners.Suite")] 7 | public class VideoJSTestSuite 8 | { 9 | public var test1:com.videojs.test.VideoJSModelTest; 10 | 11 | } 12 | } -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | VideoJS Workbench 8 | 9 | 10 | 11 | 12 | 250 | 251 | 252 | 253 |
254 |
255 | 266 |
267 | 268 |
269 |
270 | 271 |
272 |
273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 |
currentTimen/a
timen/a
defaultPlaybackRate1
durationn/a
endedn/a
pausedn/a
mutedn/a
volumen/a
seekingn/a
networkStaten/a
readyStaten/a
bufferedBytesStart0
bufferedBytesEndn/a
bytesTotal0
videoWidth0
videoHeight0
342 |
343 |
344 |
345 | 346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |

Simple Controls

355 |

Once playback has started, the SWF's control methods will work:

356 | pause()
357 | resume()
358 | seek(5)
359 |
360 |
361 |
362 | 363 | 364 | -------------------------------------------------------------------------------- /tests/manual/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } -------------------------------------------------------------------------------- /tests/manual/css/styles.css: -------------------------------------------------------------------------------- 1 | .clearfix:before, 2 | .clearfix:after { 3 | content: "\0020"; 4 | display: block; 5 | height: 0; 6 | overflow: hidden; 7 | } 8 | 9 | .clearfix:after { 10 | clear: both; 11 | } 12 | 13 | .clearfix { 14 | zoom: 1; 15 | } 16 | 17 | body{ 18 | padding-top: 50px; 19 | background: #333; 20 | color: #fff; 21 | font-family: 'Lucida Sans', 'Lucida Grande', Helvetica, Arial, sans-serif; 22 | font-size: 11px; 23 | } 24 | 25 | h1{ 26 | margin-bottom: 20px; 27 | font-size: 1.4em; 28 | } 29 | 30 | p{ 31 | margin-bottom: 20px; 32 | line-height: 1.4em; 33 | } 34 | 35 | ul{ 36 | margin-bottom: 20px; 37 | } 38 | 39 | li{ 40 | margin-left: 20px; 41 | margin-bottom: 5px; 42 | } 43 | 44 | section{ 45 | width: 900px; 46 | } 47 | 48 | #videoPlayerWrapper{ 49 | width: 480px; 50 | height: 270px; 51 | } 52 | 53 | #status{ 54 | width: 300px; 55 | height: 270px; 56 | background: #fff; 57 | position: absolute; 58 | top: 60px; 59 | left: 850px; 60 | } 61 | 62 | #configuration{ 63 | display: none; 64 | } 65 | 66 | #control{ 67 | display: none; 68 | } 69 | 70 | #videoPlayerWrapper{ 71 | width:480px; 72 | height:270px; 73 | border: 1px dashed #fff; 74 | background: #999; 75 | } 76 | 77 | div.tab-control ul{ 78 | border-bottom: 1px solid #ccc; 79 | margin-bottom: 0; 80 | height: 30px; 81 | overflow: hidden; 82 | } 83 | 84 | div.tab-control li{ 85 | display: block; 86 | float: left; 87 | } 88 | 89 | div.tab-control li a{ 90 | display: block; 91 | padding: 10px; 92 | color: #999; 93 | text-decoration: none; 94 | } 95 | 96 | div.tab-control li:first-child a{ 97 | 98 | } 99 | 100 | div.tab-control li a.selected{ 101 | color: #333; 102 | } 103 | 104 | div.tab-control div.tab-content{ 105 | display: none; 106 | overflow-y: auto; 107 | margin: 10px; 108 | height: 220px; 109 | color: #0000FF; 110 | border: 1px solid #ccc; 111 | } 112 | 113 | 114 | a.button { 115 | display: inline-block; 116 | vertical-align: middle; 117 | padding: .5em 10px; 118 | margin-bottom: 20px; 119 | border: 1px solid #555; 120 | -moz-border-radius: 4px; 121 | -webkit-border-radius: 4px; 122 | border-radius: 4px; 123 | 124 | -webkit-user-select: none; 125 | -moz-user-select: none; 126 | user-select: none; 127 | 128 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 129 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 130 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 131 | 132 | background-color: #878787; 133 | background-repeat: repeat-x; 134 | background-image: -moz-linear-gradient(#c1c1c1, #878787); 135 | background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#c1c1c1), to(#878787)); 136 | background-image: -webkit-linear-gradient(#c1c1c1, #878787); 137 | background-image: -ms-linear-gradient(#c1c1c1, #878787); 138 | background-image: linear-gradient(#c1c1c1, #878787); 139 | 140 | font-family: 'OpenSansSemibold', 'Lucida Sans Unicode', 'Lucida Grande', sans-serif; 141 | font-size: 12px; 142 | line-height: 1.5em; 143 | text-align: center; 144 | text-decoration: none; 145 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 146 | color: #ffffff; 147 | } 148 | 149 | a.button:hover { 150 | background-position: 0 -10px; 151 | text-decoration: none; 152 | } 153 | 154 | a.button:active { 155 | -moz-box-shadow: inset 0px 0px 5px rgba(0,0,0,0.5); 156 | -webkit-box-shadow: inset 0px 0px 5px rgba(0,0,0,0.5); 157 | box-shadow: inset 0px 0px 5px rgba(0,0,0,0.5); 158 | } 159 | 160 | #propertiesTable{ 161 | width: 100%; 162 | } 163 | 164 | #propertiesTable tr{ 165 | border-bottom: 1px solid #ccc; 166 | } 167 | 168 | #propertiesTable tr:last-child{ 169 | border-bottom: 0; 170 | } 171 | 172 | #propertiesTable td{ 173 | padding: 10px; 174 | } 175 | 176 | #propertiesTable td.prop{ 177 | width: 60%; 178 | text-align: right; 179 | color: #333; 180 | } 181 | 182 | #propertiesTable td.value{ 183 | width: 40%; 184 | } 185 | 186 | /* Mini Grid System */ 187 | 188 | .row{ 189 | padding: 1em 0 1em 0; 190 | margin-left: -20px; 191 | } 192 | 193 | .row::before, .row::after { 194 | display: table; 195 | content: ""; 196 | } 197 | 198 | .row::after { 199 | clear: both; 200 | } 201 | 202 | .row .span1, 203 | .row .span2, 204 | .row .span3, 205 | .row .span4, 206 | .row .span5, 207 | .row .span6{ 208 | display: inline; 209 | float: left; 210 | margin-left: 20px; 211 | -moz-border-radius: 4px; 212 | -webkit-border-radius: 4px; 213 | border-radius: 4px; 214 | } 215 | 216 | .row .span1{ 217 | width: 130px; 218 | } 219 | 220 | .row .span2{ 221 | width: 280px; 222 | } 223 | 224 | .row .span3{ 225 | width: 430px; 226 | } 227 | 228 | .row .span4{ 229 | width: 580px; 230 | } 231 | 232 | .row .span5{ 233 | width: 730px; 234 | } 235 | 236 | .row .span6{ 237 | width: 880px; 238 | } 239 | 240 | .row .offset1{ 241 | margin-left: 150px; 242 | } 243 | 244 | .row .offset2{ 245 | margin-left: 300px; 246 | } 247 | 248 | .row .offset3{ 249 | margin-left: 450px; 250 | } 251 | 252 | .row .offset4{ 253 | margin-left: 600px; 254 | } 255 | 256 | .row .offset5{ 257 | margin-left: 750px; 258 | } 259 | 260 | .white{ 261 | background: #fff; 262 | } 263 | -------------------------------------------------------------------------------- /tests/manual/img/testPattern_ussr_480x360.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videojs/video-js-swf/4b609118e090ec07682344ae421d18c92042d148/tests/manual/img/testPattern_ussr_480x360.png -------------------------------------------------------------------------------- /tests/manual/index.html: -------------------------------------------------------------------------------- 1 | manual.html
2 | manual_full.html
-------------------------------------------------------------------------------- /tests/manual/js/swfobject.js: -------------------------------------------------------------------------------- 1 | /* SWFObject v2.2 2 | is released under the MIT License 3 | */ 4 | var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab 2 | 3 | 4 | 5 | 6 | 7 | VideoJS Workbench 8 | 9 | 10 | 11 | 12 | 256 | 257 | 258 | 259 |
260 |
261 | 272 |
273 | 274 |
275 |
276 | 277 |
278 |
279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 |
currentTimen/a
timen/a
defaultPlaybackRate1
durationn/a
endedn/a
pausedn/a
mutedn/a
volumen/a
seekingn/a
networkStaten/a
readyStaten/a
bufferedBytesStart0
bufferedBytesEndn/a
bytesTotal0
videoWidth0
videoHeight0
348 |
349 |
350 |
351 | 352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |

Simple Controls

361 |

Once playback has started, the SWF's control methods will work:

362 | pause()
363 | resume()
364 | seek(5)
365 |
366 |
367 |
368 | 369 | 370 | -------------------------------------------------------------------------------- /tests/manual/manual_full.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | VideoJS Workbench 8 | 9 | 10 | 11 | 12 | 268 | 269 | 270 | 271 |
272 |
273 | 284 |
285 | 286 |
287 |
288 | 289 |
290 |
291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 |
currentTimen/a
timen/a
defaultPlaybackRate1
durationn/a
endedn/a
pausedn/a
mutedn/a
volumen/a
seekingn/a
networkStaten/a
readyStaten/a
bufferedBytesStart0
bufferedBytesEndn/a
bytesTotal0
videoWidth0
videoHeight0
360 |
361 |
362 |
363 | 364 |
365 |
366 |
367 |

Step 1: Instantiation

368 |

Create the swf object and provide the name of the JavaScript function that's called when the player has been instanced with a 'readyFunctionName' flashvars parameter.

369 |

(the src and autoplay properties are able to be set at this time, but we won't do that here)

370 | Create the SWF 371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 | 380 |
381 |
382 |
383 |

Step 2: Configuration & Playback

384 |

Once the swf has been fully instanced, we have access to all of its methods and properties.

385 |

Let's set some basic properties:

386 |
    387 |
  • eventProxyFunction: "onSWFEvent"
  • 388 |
  • errorEventProxyFunction: "onSWFErrorEvent"
  • 389 |
  • poster: "img/testPattern_ussr_480x360.png"
  • 390 |
391 | Set Properties 392 |

Next, we'll call src() to let the swf know where the media asset can be found:

393 | src("http://vjs.zencdn.net/v/oceans.mp4?") 394 |

If the SWF's autoplay property had been set to true at any point prior to this src() call, loading and playback would begin.

395 |

To begin loading without playing, call load():

396 | load() 397 |

To begin playback, call play():

398 | play() 399 |
400 |
401 |

Step 3: Control

402 |

Once playback has started, the SWF's control methods will work:

403 | pause() 404 | resume() 405 |
406 | seek(5) 407 | seek(10) 408 | seek(15) 409 |
410 | enlarge 411 | reduce 412 |
413 |
414 |
415 | 416 | 417 | 418 | 419 | 420 | -------------------------------------------------------------------------------- /tests/qunit/.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | *~ 3 | *.diff 4 | *.patch 5 | .DS_Store 6 | .settings 7 | 8 | -------------------------------------------------------------------------------- /tests/qunit/README.md: -------------------------------------------------------------------------------- 1 | [QUnit](http://docs.jquery.com/QUnit) - A JavaScript Unit Testing framework. 2 | ================================ 3 | 4 | QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery 5 | project to test its code and plugins but is capable of testing any generic 6 | JavaScript code (and even capable of testing JavaScript code on the server-side). 7 | 8 | QUnit is especially useful for regression testing: Whenever a bug is reported, 9 | write a test that asserts the existence of that particular bug. Then fix it and 10 | commit both. Every time you work on the code again, run the tests. If the bug 11 | comes up again - a regression - you'll spot it immediately and know how to fix 12 | it, because you know what code you just changed. 13 | 14 | Having good unit test coverage makes safe refactoring easy and cheap. You can 15 | run the tests after each small refactoring step and always know what change 16 | broke something. 17 | 18 | QUnit is similar to other unit testing frameworks like JUnit, but makes use of 19 | the features JavaScript provides and helps with testing code in the browser, eg. 20 | with it's stop/start facilities for testing asynchronous code. 21 | 22 | If you are interested in helping developing QUnit, you are in the right place. 23 | For related discussions, visit the 24 | [QUnit and Testing forum](http://forum.jquery.com/qunit-and-testing). 25 | 26 | Planning for a qunitjs.com site and other testing tools related work now happens 27 | on the [jQuery Testing Team planning wiki](http://jquerytesting.pbworks.com/w/page/41556026/FrontPage). 28 | -------------------------------------------------------------------------------- /tests/qunit/addons/canvas/README.md: -------------------------------------------------------------------------------- 1 | Canvas - A QUnit Addon For Testing Canvas Rendering 2 | ================================ 3 | 4 | This addon for QUnit adds a pixelEqual method that allows you to assert 5 | individual pixel values in a given canvas. 6 | 7 | Usage: 8 | 9 | pixelEqual(canvas, x, y, r, g, b, a, message) 10 | 11 | Where: 12 | 13 | * canvas: Reference to a canvas element 14 | * x, y: Coordinates of the pixel to test 15 | * r, g, b, a: The color and opacity value of the pixel that you except 16 | * message: Optional message, same as for other assertions 17 | -------------------------------------------------------------------------------- /tests/qunit/addons/canvas/canvas-test.js: -------------------------------------------------------------------------------- 1 | test("Canvas pixels", function () { 2 | var canvas = document.getElementById('qunit-canvas'), context; 3 | try { 4 | context = canvas.getContext('2d'); 5 | } catch(e) { 6 | // propably no canvas support, just exit 7 | return; 8 | } 9 | context.fillStyle = 'rgba(0, 0, 0, 0)'; 10 | context.fillRect(0, 0, 5, 5); 11 | QUnit.pixelEqual(canvas, 0, 0, 0, 0, 0, 0); 12 | context.clearRect(0,0,5,5); 13 | context.fillStyle = 'rgba(255, 0, 0, 0)'; 14 | context.fillRect(0, 0, 5, 5); 15 | QUnit.pixelEqual(canvas, 0, 0, 0, 0, 0, 0); 16 | context.clearRect(0,0,5,5); 17 | context.fillStyle = 'rgba(0, 255, 0, 0)'; 18 | context.fillRect(0, 0, 5, 5); 19 | QUnit.pixelEqual(canvas, 0, 0, 0, 0, 0, 0); 20 | context.clearRect(0,0,5,5); 21 | context.fillStyle = 'rgba(0, 0, 255, 0)'; 22 | context.fillRect(0, 0, 5, 5); 23 | QUnit.pixelEqual(canvas, 0, 0, 0, 0, 0, 0); 24 | context.clearRect(0,0,5,5); 25 | 26 | context.fillStyle = 'rgba(0, 0, 0, 0.5)'; 27 | context.fillRect(0, 0, 5, 5); 28 | QUnit.pixelEqual(canvas, 0, 0, 0, 0, 0, 127); 29 | context.clearRect(0,0,5,5); 30 | context.fillStyle = 'rgba(255, 0, 0, 0.5)'; 31 | context.fillRect(0, 0, 5, 5); 32 | QUnit.pixelEqual(canvas, 0, 0, 255, 0, 0, 127); 33 | context.clearRect(0,0,5,5); 34 | context.fillStyle = 'rgba(0, 255, 0, 0.5)'; 35 | context.fillRect(0, 0, 5, 5); 36 | QUnit.pixelEqual(canvas, 0, 0, 0, 255, 0, 127); 37 | context.clearRect(0,0,5,5); 38 | context.fillStyle = 'rgba(0, 0, 255, 0.5)'; 39 | context.fillRect(0, 0, 5, 5); 40 | QUnit.pixelEqual(canvas, 0, 0, 0, 0, 255, 127); 41 | context.clearRect(0,0,5,5); 42 | 43 | context.fillStyle = 'rgba(0, 0, 0, 0.5)'; 44 | context.fillRect(0, 0, 5, 5); 45 | QUnit.pixelEqual(canvas, 2, 2, 0, 0, 0, 127); 46 | context.clearRect(0,0,5,5); 47 | context.fillStyle = 'rgba(255, 0, 0, 0.5)'; 48 | context.fillRect(0, 0, 5, 5); 49 | QUnit.pixelEqual(canvas, 2, 2, 255, 0, 0, 127); 50 | context.clearRect(0,0,5,5); 51 | context.fillStyle = 'rgba(0, 255, 0, 0.5)'; 52 | context.fillRect(0, 0, 5, 5); 53 | QUnit.pixelEqual(canvas, 2, 2, 0, 255, 0, 127); 54 | context.clearRect(0,0,5,5); 55 | context.fillStyle = 'rgba(0, 0, 255, 0.5)'; 56 | context.fillRect(0, 0, 5, 5); 57 | QUnit.pixelEqual(canvas, 2, 2, 0, 0, 255, 127); 58 | context.clearRect(0,0,5,5); 59 | 60 | context.fillStyle = 'rgba(0, 0, 0, 1)'; 61 | context.fillRect(0, 0, 5, 5); 62 | QUnit.pixelEqual(canvas, 4, 4, 0, 0, 0, 255); 63 | context.clearRect(0,0,5,5); 64 | context.fillStyle = 'rgba(255, 0, 0, 1)'; 65 | context.fillRect(0, 0, 5, 5); 66 | QUnit.pixelEqual(canvas, 4, 4, 255, 0, 0, 255); 67 | context.clearRect(0,0,5,5); 68 | context.fillStyle = 'rgba(0, 255, 0, 1)'; 69 | context.fillRect(0, 0, 5, 5); 70 | QUnit.pixelEqual(canvas, 4, 4, 0, 255, 0, 255); 71 | context.clearRect(0,0,5,5); 72 | context.fillStyle = 'rgba(0, 0, 255, 1)'; 73 | context.fillRect(0, 0, 5, 5); 74 | QUnit.pixelEqual(canvas, 4, 4, 0, 0, 255, 255); 75 | context.clearRect(0,0,5,5); 76 | }); 77 | -------------------------------------------------------------------------------- /tests/qunit/addons/canvas/canvas.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Test Suite - Canvas Addon 6 | 7 | 8 | 9 | 10 | 11 | 12 |

QUnit Test Suite - Canvas Addon

13 |

14 |
15 |

16 |
    17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/qunit/addons/canvas/qunit-canvas.js: -------------------------------------------------------------------------------- 1 | QUnit.extend( QUnit, { 2 | pixelEqual: function(canvas, x, y, r, g, b, a, message) { 3 | var actual = Array.prototype.slice.apply(canvas.getContext('2d').getImageData(x, y, 1, 1).data), expected = [r, g, b, a]; 4 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /tests/qunit/addons/close-enough/README.md: -------------------------------------------------------------------------------- 1 | Close-Enough - A QUnit Addon For Number Approximations 2 | ================================ 3 | 4 | This addon for QUnit adds close and notClose assertion methods, to test that 5 | numbers are close enough (or different enough) from an expected number, with 6 | a specified accuracy. 7 | 8 | Usage: 9 | 10 | close(actual, expected, maxDifference, message) 11 | notClose(actual, expected, minDifference, message) 12 | 13 | Where: 14 | 15 | * maxDifference: the maximum inclusive difference allowed between the actual and expected numbers 16 | * minDifference: the minimum exclusive difference allowed between the actual and expected numbers 17 | * actual, expected, message: The usual 18 | -------------------------------------------------------------------------------- /tests/qunit/addons/close-enough/close-enough-test.js: -------------------------------------------------------------------------------- 1 | test("Close Numbers", function () { 2 | 3 | QUnit.close(7, 7, 0); 4 | QUnit.close(7, 7.1, 0.1); 5 | QUnit.close(7, 7.1, 0.2); 6 | 7 | QUnit.close(3.141, Math.PI, 0.001); 8 | QUnit.close(3.1, Math.PI, 0.1); 9 | 10 | var halfPi = Math.PI / 2; 11 | QUnit.close(halfPi, 1.57, 0.001); 12 | 13 | var sqrt2 = Math.sqrt(2); 14 | QUnit.close(sqrt2, 1.4142, 0.0001); 15 | 16 | QUnit.close(Infinity, Infinity, 1); 17 | 18 | }); 19 | 20 | test("Distant Numbers", function () { 21 | 22 | QUnit.notClose(6, 7, 0); 23 | QUnit.notClose(7, 7.2, 0.1); 24 | QUnit.notClose(7, 7.2, 0.19999999999); 25 | 26 | QUnit.notClose(3.141, Math.PI, 0.0001); 27 | QUnit.notClose(3.1, Math.PI, 0.001); 28 | 29 | var halfPi = Math.PI / 2; 30 | QUnit.notClose(halfPi, 1.57, 0.0001); 31 | 32 | var sqrt2 = Math.sqrt(2); 33 | QUnit.notClose(sqrt2, 1.4142, 0.00001); 34 | 35 | QUnit.notClose(Infinity, -Infinity, 5); 36 | 37 | }); -------------------------------------------------------------------------------- /tests/qunit/addons/close-enough/close-enough.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Test Suite - Close Enough Addon 6 | 7 | 8 | 9 | 10 | 11 | 12 |

    QUnit Test Suite - Close Enough

    13 |

    14 |
    15 |

    16 |
      17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/qunit/addons/close-enough/qunit-close-enough.js: -------------------------------------------------------------------------------- 1 | QUnit.extend( QUnit, { 2 | /** 3 | * Checks that the first two arguments are equal, or are numbers close enough to be considered equal 4 | * based on a specified maximum allowable difference. 5 | * 6 | * @example close(3.141, Math.PI, 0.001); 7 | * 8 | * @param Number actual 9 | * @param Number expected 10 | * @param Number maxDifference (the maximum inclusive difference allowed between the actual and expected numbers) 11 | * @param String message (optional) 12 | */ 13 | close: function(actual, expected, maxDifference, message) { 14 | var passes = (actual === expected) || Math.abs(actual - expected) <= maxDifference; 15 | QUnit.push(passes, actual, expected, message); 16 | }, 17 | 18 | /** 19 | * Checks that the first two arguments are numbers with differences greater than the specified 20 | * minimum difference. 21 | * 22 | * @example notClose(3.1, Math.PI, 0.001); 23 | * 24 | * @param Number actual 25 | * @param Number expected 26 | * @param Number minDifference (the minimum exclusive difference allowed between the actual and expected numbers) 27 | * @param String message (optional) 28 | */ 29 | notClose: function(actual, expected, minDifference, message) { 30 | QUnit.push(Math.abs(actual - expected) > minDifference, actual, expected, message); 31 | } 32 | }); -------------------------------------------------------------------------------- /tests/qunit/addons/composite/README.md: -------------------------------------------------------------------------------- 1 | Composite - A QUnit Addon For Running Multiple Test Files 2 | ================================ 3 | 4 | Composite is a QUnit addon that, when handed an array of files, will 5 | open each of those files inside of an iframe, run the tests and 6 | display the results as a single suite of QUnit tests. 7 | 8 | -------------------------------------------------------------------------------- /tests/qunit/addons/composite/composite-demo-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit SubsuiteRunner Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 21 | 22 | 23 | 24 |

      QUnit SubsuiteRunner Test Suite

      25 |

      26 |
      27 |

      28 |
        29 |
        30 | 31 |
        32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/qunit/addons/composite/composite-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Core Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

        QUnit Core Test Suite

        14 |

        15 |
        16 |

        17 |
          18 |
          test markup
          19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/qunit/addons/composite/composite-test.js: -------------------------------------------------------------------------------- 1 | module( "testSuites tests", (function(){ 2 | var asyncTest = QUnit.asyncTest, 3 | runSuite = QUnit.runSuite; 4 | 5 | return { 6 | setup: function(){ 7 | //proxy asyncTest and runSuite 8 | QUnit.asyncTest = window.asyncTest = function( name, callback ){ 9 | ok( true, "asyncTestCalled for each suite" ); 10 | callback(); //don't acutally create tests, just call callback 11 | }; 12 | QUnit.runSuite = window.runSuite = function(){ 13 | ok( true, "runSuite called for each suite" ); 14 | }; 15 | //ensure that subsuite's done doesn't run 16 | this.oldDone = QUnit.done; 17 | }, 18 | teardown: function(){ 19 | //restore 20 | QUnit.asyncTest = window.asyncTest = asyncTest; 21 | QUnit.runSuite = window.runSuite = runSuite; 22 | QUnit.done = this.oldDone; 23 | } 24 | }; 25 | })()); 26 | 27 | test( "proper number of asyncTest and runSuite calls", function(){ 28 | expect( 6 ); 29 | QUnit.testSuites( ["one.html", "two.html", "three.html"] ); 30 | }); 31 | 32 | test( "done callback changed", function(){ 33 | QUnit.testSuites( ["dummy.html"] ); 34 | notEqual( this.oldDone, QUnit.done, "done callback should be set" ); 35 | }); 36 | 37 | module( "testStart tests", (function(){ 38 | var id = QUnit.id; 39 | return { 40 | setup: function(){ 41 | //proxy id 42 | var fakeElem = this.fakeElem = document.createElement( "div" ); 43 | 44 | QUnit.id = function(){ 45 | return fakeElem; 46 | } 47 | }, 48 | teardown: function(){ 49 | QUnit.id = id; 50 | } 51 | }; 52 | })()); 53 | 54 | test( "running message printed", function(){ 55 | var hello = "hello world", 56 | expected = "Running " + hello + "...
           "; 57 | QUnit.testStart( {name: hello} ); 58 | equal( this.fakeElem.innerHTML, expected, "innerHTML was set correctly by testStart" ); 59 | }); 60 | 61 | module( "testDone tests", (function(){ 62 | var id = QUnit.id; 63 | return { 64 | setup: function(){ 65 | //proxy id 66 | var fakeElem = this.fakeElem = document.createElement( "div" ); 67 | fakeElem.appendChild( document.createElement( "ol" ) ); 68 | fakeElem.appendChild( document.createElement( "ol" ) ); 69 | QUnit.id = function(){ 70 | return fakeElem; 71 | } 72 | }, 73 | teardown: function(){ 74 | QUnit.id = id; 75 | } 76 | }; 77 | })()); 78 | 79 | test( "test expansions are hidden", function(){ 80 | QUnit.testDone(); 81 | equal( this.fakeElem.children[0].style.display, "none", "first ol display is none" ); 82 | equal( this.fakeElem.children[1].style.display, "none", "second ol display is none" ); 83 | }); 84 | 85 | test( "non-ol elements aren't hidden", function(){ 86 | this.fakeElem.appendChild( document.createElement( "span" ) ); 87 | 88 | QUnit.testDone(); 89 | notEqual( this.fakeElem.children[2].style.display, "none", "first ol display is none" ); 90 | }); 91 | 92 | module( "runSuite tests", (function(){ 93 | var getElementsByTagName = document.getElementsByTagName, 94 | createElement = document.createElement, 95 | runSuite = QUnit.runSuite; 96 | 97 | return { 98 | setup: function(){ 99 | //proxy getElementsByTagName and createElement 100 | var setAttributeCall = this.setAttributeCall = {}, 101 | appendChildCall = this.appendChildCall = {called: 0}, 102 | iframeLoad = this.iframeLoad = {}, 103 | iframeQUnitObject = this.iframeQUnitObject = {}, 104 | fakeElement = { 105 | appendChild: function(){appendChildCall.called++}, 106 | setAttribute: function(){setAttributeCall.args = arguments}, 107 | addEventListener: function( type, callback ){iframeLoad.callback = callback;}, 108 | contentWindow: {QUnit: iframeQUnitObject}, 109 | className: "", 110 | }; 111 | 112 | document.getElementsByTagName = function(){ 113 | return [fakeElement]; 114 | }; 115 | document.createElement = function(){ 116 | return fakeElement; 117 | } 118 | 119 | }, 120 | teardown: function(){ 121 | document.getElementsByTagName = getElementsByTagName; 122 | document.createElement = createElement; 123 | //must restore even though we didn't proxy; the runner overwrites upon first call 124 | QUnit.runSuite = runSuite; 125 | } 126 | }; 127 | })()); 128 | 129 | test( "runSuite different after first run", function(){ 130 | var before = QUnit.runSuite, 131 | after; 132 | QUnit.runSuite(); 133 | after = QUnit.runSuite; 134 | notEqual( before, after, "runSuite changed after initial run" ); 135 | }); 136 | 137 | test( "iframe only created once", function(){ 138 | QUnit.runSuite(); 139 | equal( this.appendChildCall.called, 1, "append child called once" ); 140 | QUnit.runSuite(); 141 | equal( this.appendChildCall.called, 1, "append child only ever called once" ); 142 | }); 143 | 144 | test( "iframe's QUnit object is modified when iframe source loads", function(){ 145 | var before = this.iframeQUnitObject, 146 | after; 147 | QUnit.runSuite(); 148 | this.iframeLoad.callback(); 149 | notEqual( before, after, "iframe's qunit object is modified upon load"); 150 | }); 151 | 152 | test( "iframe src set to suite passed", function(){ 153 | var pages = ["testing.html", "subsuiteRunner.html"]; 154 | QUnit.runSuite( pages[0] ); 155 | equal( this.setAttributeCall.args[0], "src", "src attribute set" ); 156 | equal( this.setAttributeCall.args[1], pages[0], "src attribute set" ); 157 | QUnit.runSuite( pages[1] ); 158 | equal( this.setAttributeCall.args[1], pages[1], "src attribute set" ); 159 | }); -------------------------------------------------------------------------------- /tests/qunit/addons/composite/dummy-qunit-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Core Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 |

          QUnit Core Test Suite

          13 |

          14 |
          15 |

          16 |
            17 |
            test markup
            18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/qunit/addons/composite/dummy-same-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Same Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 |

            QUnit Same Test Suite

            13 |

            14 |
            15 |

            16 |
              17 |
              test markup
              18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/qunit/addons/composite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Composite 6 | 7 | 8 |

              Composite

              9 |

              A QUnit Addon For Running Multiple Test Files

              10 |

              Composite is a QUnit addon that, when handed an array of 11 | files, will open each of those files inside of an iframe, run 12 | the tests and display the results as a single suite of QUnit 13 | tests.

              14 |

              Using Composite

              15 |

              To use Composite, setup a standard QUnit html page as you 16 | would with other QUnit tests. Remember to include composite.js 17 | and composite.css. Then, inside of either an external js file, 18 | or a script block call the only new method that Composite 19 | exposes, QUnit.testSuites().

              QUnit.testSuites() is 20 | passed an array of test files to run as follows:

              21 |
              22 | QUnit.testSuites([
              23 |     "test-file-1.html",
              24 |     "test-file-2.html",
              25 |     "test-file-3.html"
              26 | ]);
              27 | 		
              28 |

              Tests

              29 |

              Composite has tests of it's own.

              30 |

              31 | Composite Test: A suite which tests the implementation of composite.
              32 | Composite Demo: A suite which demoes how Compisite is bootstrapped and run. 33 |

              34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/qunit/addons/composite/qunit-composite.css: -------------------------------------------------------------------------------- 1 | iframe.qunit-subsuite{ 2 | position: fixed; 3 | bottom: 0; 4 | left: 0; 5 | 6 | margin: 0; 7 | padding: 0; 8 | border-width: 1px 0 0; 9 | height: 45%; 10 | width: 100%; 11 | 12 | background: #fff; 13 | } -------------------------------------------------------------------------------- /tests/qunit/addons/composite/qunit-composite.js: -------------------------------------------------------------------------------- 1 | (function( QUnit ) { 2 | 3 | var subsuiteFrame; 4 | 5 | QUnit.extend( QUnit, { 6 | testSuites: function( suites ) { 7 | for ( var i = 0; i < suites.length; i++ ) { 8 | (function( suite ) { 9 | asyncTest( suite, function() { 10 | QUnit.runSuite( suite ); 11 | }); 12 | }( suites[i] ) ); 13 | } 14 | QUnit.done = function() { 15 | subsuiteFrame.style.display = "none"; 16 | }; 17 | }, 18 | 19 | testStart: function( data ) { 20 | // update the test status to show which test suite is running 21 | QUnit.id( "qunit-testresult" ).innerHTML = "Running " + data.name + "...
               "; 22 | }, 23 | 24 | testDone: function() { 25 | var current = QUnit.id( this.config.current.id ), 26 | children = current.children; 27 | 28 | // undo the auto-expansion of failed tests 29 | for ( var i = 0; i < children.length; i++ ) { 30 | if ( children[i].nodeName === "OL" ) { 31 | children[i].style.display = "none"; 32 | } 33 | } 34 | }, 35 | 36 | runSuite: function( suite ) { 37 | var body = document.getElementsByTagName( "body" )[0], 38 | iframe = subsuiteFrame = document.createElement( "iframe" ), 39 | iframeWin; 40 | 41 | iframe.className = "qunit-subsuite"; 42 | body.appendChild( iframe ); 43 | 44 | function onIframeLoad() { 45 | var module, test, 46 | count = 0; 47 | 48 | QUnit.extend( iframeWin.QUnit, { 49 | moduleStart: function( data ) { 50 | // capture module name for messages 51 | module = data.name; 52 | }, 53 | 54 | testStart: function( data ) { 55 | // capture test name for messages 56 | test = data.name; 57 | }, 58 | 59 | log: function( data ) { 60 | // pass all test details through to the main page 61 | var message = module + ": " + test + ": " + data.message; 62 | expect( ++count ); 63 | QUnit.push( data.result, data.actual, data.expected, message ); 64 | }, 65 | 66 | done: function() { 67 | // start the wrapper test from the main page 68 | start(); 69 | } 70 | }); 71 | } 72 | QUnit.addEvent( iframe, "load", onIframeLoad ); 73 | 74 | iframeWin = iframe.contentWindow; 75 | iframe.setAttribute( "src", suite ); 76 | 77 | this.runSuite = function( suite ) { 78 | iframe.setAttribute( "src", suite ); 79 | }; 80 | } 81 | }); 82 | }( QUnit ) ); 83 | -------------------------------------------------------------------------------- /tests/qunit/addons/step/README.md: -------------------------------------------------------------------------------- 1 | QUnit.step() - A QUnit Addon For Testing execution in order 2 | ============================================================ 3 | 4 | This addon for QUnit adds a step method that allows you to assert 5 | the proper sequence in which the code should execute. 6 | 7 | Example: 8 | 9 | test("example test", function () { 10 | function x() { 11 | QUnit.step(2, "function y should be called first"); 12 | } 13 | function y() { 14 | QUnit.step(1); 15 | } 16 | y(); 17 | x(); 18 | }); -------------------------------------------------------------------------------- /tests/qunit/addons/step/qunit-step.js: -------------------------------------------------------------------------------- 1 | QUnit.extend( QUnit, { 2 | 3 | /** 4 | * Check the sequence/order 5 | * 6 | * @example step(1); setTimeout(function () { step(3); }, 100); step(2); 7 | * @param Number expected The excepted step within the test() 8 | * @param String message (optional) 9 | */ 10 | step: function (expected, message) { 11 | this.config.current.step++; // increment internal step counter. 12 | if (typeof message == "undefined") { 13 | message = "step " + expected; 14 | } 15 | var actual = this.config.current.step; 16 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); 17 | } 18 | }); 19 | 20 | /** 21 | * Reset the step counter for every test() 22 | */ 23 | QUnit.testStart(function () { 24 | this.config.current.step = 0; 25 | }); 26 | -------------------------------------------------------------------------------- /tests/qunit/addons/step/step-test.js: -------------------------------------------------------------------------------- 1 | module('Step Addon'); 2 | test("step", 3, function () { 3 | QUnit.step(1, "step starts at 1"); 4 | setTimeout(function () { 5 | start(); 6 | QUnit.step(3); 7 | }, 100); 8 | QUnit.step(2, "before the setTimeout callback is run"); 9 | stop(); 10 | }); 11 | test("step counter", 1, function () { 12 | QUnit.step(1, "each test has its own step counter"); 13 | }); -------------------------------------------------------------------------------- /tests/qunit/addons/step/step.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Test Suite - Step Addon 6 | 7 | 8 | 9 | 10 | 11 | 12 |

              QUnit Test Suite - Step Addon

              13 |

              14 |
              15 |

              16 |
                17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/qunit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qunit", 3 | "author": "The jQuery Project", 4 | "contributors": [ 5 | { 6 | "name": "John Resig", 7 | "email": "jeresig@gmail.com", 8 | "url": "http://ejohn.org/" 9 | }, 10 | { 11 | "name": "Jörn Zaefferer", 12 | "email": "joern.zaefferer@googlemail.com", 13 | "url": "http://bassistance.de/" 14 | }], 15 | "url": "http://docs.jquery.com/QUnit", 16 | "repositories" : [{ 17 | "type": "git", 18 | "url": "https://github.com/jquery/qunit.git" 19 | }], 20 | "license": { 21 | "name": "MIT", 22 | "url": "http://www.opensource.org/licenses/mit-license.php" 23 | }, 24 | "description": "An easy-to-use JavaScript Unit Testing framework.", 25 | "keywords": [ "testing", "unit", "jquery" ], 26 | "main": "qunit/qunit.js" 27 | } 28 | -------------------------------------------------------------------------------- /tests/qunit/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 15px 15px 0 0; 42 | -moz-border-radius: 15px 15px 0 0; 43 | -webkit-border-top-right-radius: 15px; 44 | -webkit-border-top-left-radius: 15px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-banner { 58 | height: 5px; 59 | } 60 | 61 | #qunit-testrunner-toolbar { 62 | padding: 0.5em 0 0.5em 2em; 63 | color: #5E740B; 64 | background-color: #eee; 65 | } 66 | 67 | #qunit-userAgent { 68 | padding: 0.5em 0 0.5em 2.5em; 69 | background-color: #2b81af; 70 | color: #fff; 71 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 72 | } 73 | 74 | 75 | /** Tests: Pass/Fail */ 76 | 77 | #qunit-tests { 78 | list-style-position: inside; 79 | } 80 | 81 | #qunit-tests li { 82 | padding: 0.4em 0.5em 0.4em 2.5em; 83 | border-bottom: 1px solid #fff; 84 | list-style-position: inside; 85 | } 86 | 87 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 88 | display: none; 89 | } 90 | 91 | #qunit-tests li strong { 92 | cursor: pointer; 93 | } 94 | 95 | #qunit-tests li a { 96 | padding: 0.5em; 97 | color: #c2ccd1; 98 | text-decoration: none; 99 | } 100 | #qunit-tests li a:hover, 101 | #qunit-tests li a:focus { 102 | color: #000; 103 | } 104 | 105 | #qunit-tests ol { 106 | margin-top: 0.5em; 107 | padding: 0.5em; 108 | 109 | background-color: #fff; 110 | 111 | border-radius: 15px; 112 | -moz-border-radius: 15px; 113 | -webkit-border-radius: 15px; 114 | 115 | box-shadow: inset 0px 2px 13px #999; 116 | -moz-box-shadow: inset 0px 2px 13px #999; 117 | -webkit-box-shadow: inset 0px 2px 13px #999; 118 | } 119 | 120 | #qunit-tests table { 121 | border-collapse: collapse; 122 | margin-top: .2em; 123 | } 124 | 125 | #qunit-tests th { 126 | text-align: right; 127 | vertical-align: top; 128 | padding: 0 .5em 0 0; 129 | } 130 | 131 | #qunit-tests td { 132 | vertical-align: top; 133 | } 134 | 135 | #qunit-tests pre { 136 | margin: 0; 137 | white-space: pre-wrap; 138 | word-wrap: break-word; 139 | } 140 | 141 | #qunit-tests del { 142 | background-color: #e0f2be; 143 | color: #374e0c; 144 | text-decoration: none; 145 | } 146 | 147 | #qunit-tests ins { 148 | background-color: #ffcaca; 149 | color: #500; 150 | text-decoration: none; 151 | } 152 | 153 | /*** Test Counts */ 154 | 155 | #qunit-tests b.counts { color: black; } 156 | #qunit-tests b.passed { color: #5E740B; } 157 | #qunit-tests b.failed { color: #710909; } 158 | 159 | #qunit-tests li li { 160 | margin: 0.5em; 161 | padding: 0.4em 0.5em 0.4em 0.5em; 162 | background-color: #fff; 163 | border-bottom: none; 164 | list-style-position: inside; 165 | } 166 | 167 | /*** Passing Styles */ 168 | 169 | #qunit-tests li li.pass { 170 | color: #5E740B; 171 | background-color: #fff; 172 | border-left: 26px solid #C6E746; 173 | } 174 | 175 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 176 | #qunit-tests .pass .test-name { color: #366097; } 177 | 178 | #qunit-tests .pass .test-actual, 179 | #qunit-tests .pass .test-expected { color: #999999; } 180 | 181 | #qunit-banner.qunit-pass { background-color: #C6E746; } 182 | 183 | /*** Failing Styles */ 184 | 185 | #qunit-tests li li.fail { 186 | color: #710909; 187 | background-color: #fff; 188 | border-left: 26px solid #EE5757; 189 | white-space: pre; 190 | } 191 | 192 | #qunit-tests > li:last-child { 193 | border-radius: 0 0 15px 15px; 194 | -moz-border-radius: 0 0 15px 15px; 195 | -webkit-border-bottom-right-radius: 15px; 196 | -webkit-border-bottom-left-radius: 15px; 197 | } 198 | 199 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 200 | #qunit-tests .fail .test-name, 201 | #qunit-tests .fail .module-name { color: #000000; } 202 | 203 | #qunit-tests .fail .test-actual { color: #EE5757; } 204 | #qunit-tests .fail .test-expected { color: green; } 205 | 206 | #qunit-banner.qunit-fail { background-color: #EE5757; } 207 | 208 | 209 | /** Result */ 210 | 211 | #qunit-testresult { 212 | padding: 0.5em 0.5em 0.5em 2.5em; 213 | 214 | color: #2b81af; 215 | background-color: #D2E0E6; 216 | 217 | border-bottom: 1px solid white; 218 | } 219 | 220 | /** Fixture */ 221 | 222 | #qunit-fixture { 223 | position: absolute; 224 | top: -10000px; 225 | left: -10000px; 226 | } 227 | -------------------------------------------------------------------------------- /tests/qunit/test/headless.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QUnit Test Suite 5 | 6 | 7 | 8 | 9 | 20 | 21 | 22 |
                test markup
                23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/qunit/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 |

                QUnit Test Suite

                13 |

                14 |
                15 |

                16 |
                  17 |
                  test markup
                  18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/qunit/test/logs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QUnit Test Suite 5 | 6 | 7 | 8 | 9 | 10 |

                  QUnit Test Suite

                  11 |

                  12 |
                  13 |

                  14 |
                    15 |
                    test markup
                    16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/qunit/test/logs.js: -------------------------------------------------------------------------------- 1 | // TODO disable reordering for this suite! 2 | 3 | 4 | var begin = 0, 5 | moduleStart = 0, 6 | moduleDone = 0, 7 | testStart = 0, 8 | testDone = 0, 9 | log = 0, 10 | moduleContext, 11 | moduleDoneContext, 12 | testContext, 13 | testDoneContext, 14 | logContext; 15 | 16 | QUnit.begin(function() { 17 | begin++; 18 | }); 19 | QUnit.done(function() { 20 | }); 21 | QUnit.moduleStart(function(context) { 22 | moduleStart++; 23 | moduleContext = context; 24 | }); 25 | QUnit.moduleDone(function(context) { 26 | moduleDone++; 27 | moduleDoneContext = context; 28 | }); 29 | QUnit.testStart(function(context) { 30 | testStart++; 31 | testContext = context; 32 | }); 33 | QUnit.testDone(function(context) { 34 | testDone++; 35 | testDoneContext = context; 36 | }); 37 | QUnit.log(function(context) { 38 | log++; 39 | logContext = context; 40 | }); 41 | 42 | var logs = ["begin", "testStart", "testDone", "log", "moduleStart", "moduleDone", "done"]; 43 | for (var i = 0; i < logs.length; i++) { 44 | (function() { 45 | var log = logs[i]; 46 | QUnit[log](function() { 47 | console.log(log, arguments); 48 | }); 49 | })(); 50 | } 51 | 52 | module("logs1"); 53 | 54 | test("test1", 13, function() { 55 | equal(begin, 1); 56 | equal(moduleStart, 1); 57 | equal(testStart, 1); 58 | equal(testDone, 0); 59 | equal(moduleDone, 0); 60 | 61 | deepEqual(logContext, { 62 | result: true, 63 | message: undefined, 64 | actual: 0, 65 | expected: 0 66 | }); 67 | equal("foo", "foo", "msg"); 68 | deepEqual(logContext, { 69 | result: true, 70 | message: "msg", 71 | actual: "foo", 72 | expected: "foo" 73 | }); 74 | strictEqual(testDoneContext, undefined); 75 | deepEqual(testContext, { 76 | module: "logs1", 77 | name: "test1" 78 | }); 79 | strictEqual(moduleDoneContext, undefined); 80 | deepEqual(moduleContext, { 81 | name: "logs1" 82 | }); 83 | 84 | equal(log, 12); 85 | }); 86 | test("test2", 10, function() { 87 | equal(begin, 1); 88 | equal(moduleStart, 1); 89 | equal(testStart, 2); 90 | equal(testDone, 1); 91 | equal(moduleDone, 0); 92 | 93 | deepEqual(testDoneContext, { 94 | module: "logs1", 95 | name: "test1", 96 | failed: 0, 97 | passed: 13, 98 | total: 13 99 | }); 100 | deepEqual(testContext, { 101 | module: "logs1", 102 | name: "test2" 103 | }); 104 | strictEqual(moduleDoneContext, undefined); 105 | deepEqual(moduleContext, { 106 | name: "logs1" 107 | }); 108 | 109 | equal(log, 22); 110 | }); 111 | 112 | module("logs2"); 113 | 114 | test("test1", 9, function() { 115 | equal(begin, 1); 116 | equal(moduleStart, 2); 117 | equal(testStart, 3); 118 | equal(testDone, 2); 119 | equal(moduleDone, 1); 120 | 121 | deepEqual(testContext, { 122 | module: "logs2", 123 | name: "test1" 124 | }); 125 | deepEqual(moduleDoneContext, { 126 | name: "logs1", 127 | failed: 0, 128 | passed: 23, 129 | total: 23 130 | }); 131 | deepEqual(moduleContext, { 132 | name: "logs2" 133 | }); 134 | 135 | equal(log, 31); 136 | }); 137 | test("test2", 8, function() { 138 | equal(begin, 1); 139 | equal(moduleStart, 2); 140 | equal(testStart, 4); 141 | equal(testDone, 3); 142 | equal(moduleDone, 1); 143 | 144 | deepEqual(testContext, { 145 | module: "logs2", 146 | name: "test2" 147 | }); 148 | deepEqual(moduleContext, { 149 | name: "logs2" 150 | }); 151 | 152 | equal(log, 39); 153 | }); 154 | -------------------------------------------------------------------------------- /tests/qunit/test/test.js: -------------------------------------------------------------------------------- 1 | test("module without setup/teardown (default)", function() { 2 | expect(1); 3 | ok(true); 4 | }); 5 | 6 | test("expect in test", 3, function() { 7 | ok(true); 8 | ok(true); 9 | ok(true); 10 | }); 11 | 12 | test("expect in test", 1, function() { 13 | ok(true); 14 | }); 15 | 16 | module("setup test", { 17 | setup: function() { 18 | ok(true); 19 | } 20 | }); 21 | 22 | test("module with setup", function() { 23 | expect(2); 24 | ok(true); 25 | }); 26 | 27 | test("module with setup, expect in test call", 2, function() { 28 | ok(true); 29 | }); 30 | 31 | var state; 32 | 33 | module("setup/teardown test", { 34 | setup: function() { 35 | state = true; 36 | ok(true); 37 | }, 38 | teardown: function() { 39 | ok(true); 40 | } 41 | }); 42 | 43 | test("module with setup/teardown", function() { 44 | expect(3); 45 | ok(true); 46 | }); 47 | 48 | module("setup/teardown test 2"); 49 | 50 | test("module without setup/teardown", function() { 51 | expect(1); 52 | ok(true); 53 | }); 54 | 55 | if (typeof setTimeout !== 'undefined') { 56 | state = 'fail'; 57 | 58 | module("teardown and stop", { 59 | teardown: function() { 60 | equal(state, "done", "Test teardown."); 61 | } 62 | }); 63 | 64 | test("teardown must be called after test ended", function() { 65 | expect(1); 66 | stop(); 67 | setTimeout(function() { 68 | state = "done"; 69 | start(); 70 | }, 13); 71 | }); 72 | 73 | test("parameter passed to stop increments semaphore n times", function() { 74 | expect(1); 75 | stop(3); 76 | setTimeout(function() { 77 | state = "not enough starts"; 78 | start(), start(); 79 | }, 13); 80 | setTimeout(function() { 81 | state = "done"; 82 | start(); 83 | }, 15); 84 | }); 85 | 86 | test("parameter passed to start decrements semaphore n times", function() { 87 | expect(1); 88 | stop(), stop(), stop(); 89 | setTimeout(function() { 90 | state = "done"; 91 | start(3); 92 | }, 18); 93 | }); 94 | 95 | module("async setup test", { 96 | setup: function() { 97 | stop(); 98 | setTimeout(function(){ 99 | ok(true); 100 | start(); 101 | }, 500); 102 | } 103 | }); 104 | 105 | asyncTest("module with async setup", function() { 106 | expect(2); 107 | ok(true); 108 | start(); 109 | }); 110 | 111 | module("async teardown test", { 112 | teardown: function() { 113 | stop(); 114 | setTimeout(function(){ 115 | ok(true); 116 | start(); 117 | }, 500); 118 | } 119 | }); 120 | 121 | asyncTest("module with async teardown", function() { 122 | expect(2); 123 | ok(true); 124 | start(); 125 | }); 126 | 127 | module("asyncTest"); 128 | 129 | asyncTest("asyncTest", function() { 130 | expect(2); 131 | ok(true); 132 | setTimeout(function() { 133 | state = "done"; 134 | ok(true); 135 | start(); 136 | }, 13); 137 | }); 138 | 139 | asyncTest("asyncTest", 2, function() { 140 | ok(true); 141 | setTimeout(function() { 142 | state = "done"; 143 | ok(true); 144 | start(); 145 | }, 13); 146 | }); 147 | 148 | test("sync", 2, function() { 149 | stop(); 150 | setTimeout(function() { 151 | ok(true); 152 | start(); 153 | }, 13); 154 | stop(); 155 | setTimeout(function() { 156 | ok(true); 157 | start(); 158 | }, 125); 159 | }); 160 | 161 | test("test synchronous calls to stop", 2, function() { 162 | stop(); 163 | setTimeout(function(){ 164 | ok(true, 'first'); 165 | start(); 166 | stop(); 167 | setTimeout(function(){ 168 | ok(true, 'second'); 169 | start(); 170 | }, 150); 171 | }, 150); 172 | }); 173 | } 174 | 175 | module("save scope", { 176 | setup: function() { 177 | this.foo = "bar"; 178 | }, 179 | teardown: function() { 180 | deepEqual(this.foo, "bar"); 181 | } 182 | }); 183 | test("scope check", function() { 184 | expect(2); 185 | deepEqual(this.foo, "bar"); 186 | }); 187 | 188 | module("simple testEnvironment setup", { 189 | foo: "bar", 190 | bugid: "#5311" // example of meta-data 191 | }); 192 | test("scope check", function() { 193 | deepEqual(this.foo, "bar"); 194 | }); 195 | test("modify testEnvironment",function() { 196 | this.foo="hamster"; 197 | }); 198 | test("testEnvironment reset for next test",function() { 199 | deepEqual(this.foo, "bar"); 200 | }); 201 | 202 | module("testEnvironment with object", { 203 | options:{ 204 | recipe:"soup", 205 | ingredients:["hamster","onions"] 206 | } 207 | }); 208 | test("scope check", function() { 209 | deepEqual(this.options, {recipe:"soup",ingredients:["hamster","onions"]}) ; 210 | }); 211 | test("modify testEnvironment",function() { 212 | // since we do a shallow copy, the testEnvironment can be modified 213 | this.options.ingredients.push("carrots"); 214 | }); 215 | test("testEnvironment reset for next test",function() { 216 | deepEqual(this.options, {recipe:"soup",ingredients:["hamster","onions","carrots"]}, "Is this a bug or a feature? Could do a deep copy") ; 217 | }); 218 | 219 | 220 | module("testEnvironment tests"); 221 | 222 | function makeurl() { 223 | var testEnv = QUnit.current_testEnvironment; 224 | var url = testEnv.url || 'http://example.com/search'; 225 | var q = testEnv.q || 'a search test'; 226 | return url + '?q='+encodeURIComponent(q); 227 | } 228 | 229 | test("makeurl working",function() { 230 | equal( QUnit.current_testEnvironment, this, 'The current testEnvironment is global'); 231 | equal( makeurl(), 'http://example.com/search?q=a%20search%20test', 'makeurl returns a default url if nothing specified in the testEnvironment'); 232 | }); 233 | 234 | module("testEnvironment with makeurl settings", { 235 | url: 'http://google.com/', 236 | q: 'another_search_test' 237 | }); 238 | test("makeurl working with settings from testEnvironment", function() { 239 | equal( makeurl(), 'http://google.com/?q=another_search_test', 'rather than passing arguments, we use test metadata to form the url'); 240 | }); 241 | test("each test can extend the module testEnvironment", { 242 | q:'hamstersoup' 243 | }, function() { 244 | equal( makeurl(), 'http://google.com/?q=hamstersoup', 'url from module, q from test'); 245 | }); 246 | 247 | module("jsDump"); 248 | test("jsDump output", function() { 249 | equals( QUnit.jsDump.parse([1, 2]), "[\n 1,\n 2\n]" ); 250 | equals( QUnit.jsDump.parse({top: 5, left: 0}), "{\n \"top\": 5,\n \"left\": 0\n}" ); 251 | if (typeof document !== 'undefined' && document.getElementById("qunit-header")) { 252 | equals( QUnit.jsDump.parse(document.getElementById("qunit-header")), "

                    " ); 253 | equals( QUnit.jsDump.parse(document.getElementsByTagName("h1")), "[\n

                    \n]" ); 254 | } 255 | }); 256 | 257 | module("assertions"); 258 | test("raises",function() { 259 | function CustomError( message ) { 260 | this.message = message; 261 | } 262 | 263 | CustomError.prototype.toString = function() { 264 | return this.message; 265 | }; 266 | 267 | raises( 268 | function() { 269 | throw "error" 270 | } 271 | ); 272 | 273 | raises( 274 | function() { 275 | throw "error" 276 | }, 277 | 'raises with just a message, no expected' 278 | ); 279 | 280 | raises( 281 | function() { 282 | throw new CustomError(); 283 | }, 284 | CustomError, 285 | 'raised error is an instance of CustomError' 286 | ); 287 | 288 | raises( 289 | function() { 290 | throw new CustomError("some error description"); 291 | }, 292 | /description/, 293 | "raised error message contains 'description'" 294 | ); 295 | 296 | raises( 297 | function() { 298 | throw new CustomError("some error description"); 299 | }, 300 | function( err ) { 301 | if ( (err instanceof CustomError) && /description/.test(err) ) { 302 | return true; 303 | } 304 | }, 305 | "custom validation function" 306 | ); 307 | 308 | }); 309 | 310 | if (typeof document !== "undefined") { 311 | 312 | module("fixture"); 313 | test("setup", function() { 314 | document.getElementById("qunit-fixture").innerHTML = "foobar"; 315 | }); 316 | test("basics", function() { 317 | equal( document.getElementById("qunit-fixture").innerHTML, "test markup", "automatically reset" ); 318 | }); 319 | 320 | } 321 | 322 | module("custom assertions"); 323 | (function() { 324 | function mod2(value, expected, message) { 325 | var actual = value % 2; 326 | QUnit.push(actual == expected, actual, expected, message); 327 | } 328 | test("mod2", function() { 329 | mod2(2, 0, "2 % 2 == 0"); 330 | mod2(3, 1, "3 % 2 == 1"); 331 | }) 332 | })(); 333 | 334 | 335 | module("recursions"); 336 | 337 | function Wrap(x) { 338 | this.wrap = x; 339 | if (x == undefined) this.first = true; 340 | } 341 | 342 | function chainwrap(depth, first, prev) { 343 | depth = depth || 0; 344 | var last = prev || new Wrap(); 345 | first = first || last; 346 | 347 | if (depth == 1) { 348 | first.wrap = last; 349 | } 350 | if (depth > 1) { 351 | last = chainwrap(depth-1, first, new Wrap(last)); 352 | } 353 | 354 | return last; 355 | } 356 | 357 | test("check jsDump recursion", function() { 358 | expect(4); 359 | 360 | var noref = chainwrap(0); 361 | var nodump = QUnit.jsDump.parse(noref); 362 | equal(nodump, '{\n "wrap": undefined,\n "first": true\n}'); 363 | 364 | var selfref = chainwrap(1); 365 | var selfdump = QUnit.jsDump.parse(selfref); 366 | equal(selfdump, '{\n "wrap": recursion(-1),\n "first": true\n}'); 367 | 368 | var parentref = chainwrap(2); 369 | var parentdump = QUnit.jsDump.parse(parentref); 370 | equal(parentdump, '{\n "wrap": {\n "wrap": recursion(-2),\n "first": true\n }\n}'); 371 | 372 | var circref = chainwrap(10); 373 | var circdump = QUnit.jsDump.parse(circref); 374 | ok(new RegExp("recursion\\(-10\\)").test(circdump), "(" +circdump + ") should show -10 recursion level"); 375 | }); 376 | 377 | test("check (deep-)equal recursion", function() { 378 | var noRecursion = chainwrap(0); 379 | equal(noRecursion, noRecursion, "I should be equal to me."); 380 | deepEqual(noRecursion, noRecursion, "... and so in depth."); 381 | 382 | var selfref = chainwrap(1); 383 | equal(selfref, selfref, "Even so if I nest myself."); 384 | deepEqual(selfref, selfref, "... into the depth."); 385 | 386 | var circref = chainwrap(10); 387 | equal(circref, circref, "Or hide that through some levels of indirection."); 388 | deepEqual(circref, circref, "... and checked on all levels!"); 389 | }); 390 | 391 | 392 | test('Circular reference with arrays', function() { 393 | 394 | // pure array self-ref 395 | var arr = []; 396 | arr.push(arr); 397 | 398 | var arrdump = QUnit.jsDump.parse(arr); 399 | 400 | equal(arrdump, '[\n recursion(-1)\n]'); 401 | equal(arr, arr[0], 'no endless stack when trying to dump arrays with circular ref'); 402 | 403 | 404 | // mix obj-arr circular ref 405 | var obj = {}; 406 | var childarr = [obj]; 407 | obj.childarr = childarr; 408 | 409 | var objdump = QUnit.jsDump.parse(obj); 410 | var childarrdump = QUnit.jsDump.parse(childarr); 411 | 412 | equal(objdump, '{\n "childarr": [\n recursion(-2)\n ]\n}'); 413 | equal(childarrdump, '[\n {\n "childarr": recursion(-2)\n }\n]'); 414 | 415 | equal(obj.childarr, childarr, 'no endless stack when trying to dump array/object mix with circular ref'); 416 | equal(childarr[0], obj, 'no endless stack when trying to dump array/object mix with circular ref'); 417 | 418 | }); 419 | 420 | 421 | test('Circular reference - test reported by soniciq in #105', function() { 422 | var MyObject = function() {}; 423 | MyObject.prototype.parent = function(obj) { 424 | if (obj === undefined) { return this._parent; } 425 | this._parent = obj; 426 | }; 427 | MyObject.prototype.children = function(obj) { 428 | if (obj === undefined) { return this._children; } 429 | this._children = obj; 430 | }; 431 | 432 | var a = new MyObject(), 433 | b = new MyObject(); 434 | 435 | var barr = [b]; 436 | a.children(barr); 437 | b.parent(a); 438 | 439 | equal(a.children(), barr); 440 | deepEqual(a.children(), [b]); 441 | }); 442 | 443 | 444 | 445 | 446 | (function() { 447 | var reset = QUnit.reset; 448 | function afterTest() { 449 | ok( false, "reset should not modify test status" ); 450 | } 451 | module("reset"); 452 | test("reset runs assertions", function() { 453 | QUnit.reset = function() { 454 | afterTest(); 455 | reset.apply( this, arguments ); 456 | }; 457 | }); 458 | test("reset runs assertions2", function() { 459 | QUnit.reset = reset; 460 | }); 461 | })(); 462 | 463 | module("noglobals", { 464 | teardown: function() { 465 | delete window.badGlobalVariableIntroducedInTest; 466 | } 467 | }); 468 | test("let teardown clean up globals", function() { 469 | // this test will always pass if run without ?noglobals=true 470 | window.badGlobalVariableIntroducedInTest = true; 471 | }); 472 | -------------------------------------------------------------------------------- /tests/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 |

                    QUnit Test Suite

                    22 |

                    23 |
                    24 |

                    25 |
                      26 |
                      test markup
                      27 | 28 |
                      29 | 30 |
                      31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | var uid = 0; 2 | 3 | function createSWF(e){ 4 | 5 | var flashvars = { 6 | readyFunction: "onSWFReady", 7 | eventProxyFunction: "onSWFEvent", 8 | errorEventProxyFunction: "onSWFErrorEvent", 9 | src: "http://vjs.zencdn.net/v/oceans.mp4", 10 | autoplay: false, 11 | preload: 'auto', 12 | poster: "http://vjs.zencdn.net/v/oceans.png" 13 | }; 14 | 15 | var params = { 16 | allowScriptAccess: "always", 17 | bgcolor: "#000000" 18 | }; 19 | 20 | var attributes = { 21 | id: "videoPlayer"+uid, 22 | name: "videoPlayer"+uid 23 | }; 24 | 25 | swfobject.embedSWF("../dist/video-js.swf", "videoPlayer"+uid, "100%", "100%", "10.3", "", flashvars, params, attributes); 26 | } 27 | 28 | function swfSetup(){ 29 | stop(); 30 | 31 | window.onSWFReady = $.proxy(function(swfID){ 32 | // console.log("onSWFReady", swfID); 33 | this.box = document.getElementById(swfID+"_box"); 34 | this.swf = document.getElementById(swfID); 35 | start(); 36 | }, this); 37 | 38 | window.onSWFEvent = function(swfID, eventName){ 39 | console.log("onSWFEvent", swfID, eventName); 40 | 41 | // Triggering on outer div because triggering in object wasn't working for some reason. 42 | $("#"+swfID+"_box").trigger(eventName); 43 | }; 44 | 45 | // Bind events to outer box. 46 | this.on = function(eventName, fn){ 47 | $(this.box).bind(eventName, $.proxy(fn, this)); 48 | } 49 | 50 | // Custom methods for failing a test after a certain amount of time 51 | this.failIn = function(ms){ this.failOutID = setTimeout(function(){ start(); }, ms); }; 52 | this.cancelFail = function(){ clearTimeout(this.failOutID); }; 53 | 54 | // Embed new box and placeholder for swf 55 | uid++; 56 | var id = "videoPlayer"+uid; 57 | $("#custom-fixture").append("
                      ") 58 | 59 | createSWF(); 60 | } 61 | 62 | function swfTeardown(){ 63 | swfobject.removeSWF(this.swf.id); 64 | delete this.swf; 65 | $("#custom-fixture").html(""); 66 | } 67 | 68 | 69 | module("SWF Tests", { 70 | setup: swfSetup, 71 | teardown: swfTeardown 72 | }); 73 | 74 | test("SWF Set Up & Ready", 2, function() { 75 | ok(this.swf, "Player has been set up and is ready."); 76 | ok(typeof this.swf.vjs_play == 'function', "API Methods are available on ready."); 77 | }); 78 | 79 | // Pause after playing event 80 | test("Pause After 'playing' Event", 1, function() { 81 | stop(); 82 | 83 | // Wait for playing even then call pause() 84 | this.on("playing", function(){ 85 | this.swf.vjs_pause(); 86 | }); 87 | 88 | this.on("pause", function(){ 89 | ok(true, "Player pauses."); 90 | 91 | this.cancelFail(); 92 | start(); 93 | }); 94 | 95 | // Fail after 5 seconds if it doesn't work 96 | this.failIn(3000); 97 | 98 | this.swf.vjs_play(); 99 | }); 100 | 101 | 102 | // Play Method 103 | test("Play", 1, function() { 104 | stop(); 105 | 106 | this.on("playing", function(){ 107 | ok(true, "Player plays."); 108 | start(); 109 | }); 110 | 111 | this.swf.vjs_play(); 112 | }); 113 | 114 | // 'seeked' event 115 | // Commented-out as this only works intermittently. Sometimes the "seeked" 116 | // event doesn't happen 117 | /*test("Seeked event fires after time change", 1, function() { 118 | stop(); 119 | this.on("loadeddata", function(){ 120 | this.swf.vjs_pause(); 121 | this.ct = this.swf.vjs_getProperty("currentTime"); 122 | 123 | this.on("seeked", function(){ 124 | 125 | ok(this.ct != this.swf.vjs_getProperty("currentTime"), "currentTime changed"); 126 | start(); 127 | }); 128 | 129 | this.swf.vjs_setProperty("currentTime", 30); 130 | }); 131 | 132 | this.swf.vjs_play(); 133 | }); 134 | */ 135 | 136 | // Commented out for unknown reasons... 137 | 138 | // /* Methods 139 | // ================================================================================ */ 140 | // module("API Methods", { 141 | // setup: playerSetup, 142 | // teardown: playerTeardown 143 | // }); 144 | // 145 | // function failOnEnded() { 146 | // this.player.one("ended", _V_.proxy(this, function(){ 147 | // start(); 148 | // })); 149 | // } 150 | // 151 | // // Play Method 152 | // test("play()", 1, function() { 153 | // stop(); 154 | // 155 | // this.player.one("playing", _V_.proxy(this, function(){ 156 | // ok(true); 157 | // start(); 158 | // })); 159 | // 160 | // this.player.play(); 161 | // 162 | // failOnEnded.call(this); 163 | // }); 164 | // 165 | // // Pause Method 166 | // test("pause()", 1, function() { 167 | // stop(); 168 | // 169 | // // Flash doesn't currently like calling pause immediately after 'playing'. 170 | // this.player.one("timeupdate", _V_.proxy(this, function(){ 171 | // 172 | // this.player.pause(); 173 | // 174 | // })); 175 | // 176 | // this.player.addEvent("pause", _V_.proxy(this, function(){ 177 | // ok(true); 178 | // start(); 179 | // })); 180 | // 181 | // this.player.play(); 182 | // }); 183 | // 184 | // // Paused Method 185 | // test("paused()", 2, function() { 186 | // stop(); 187 | // 188 | // this.player.one("timeupdate", _V_.proxy(this, function(){ 189 | // equal(this.player.paused(), false); 190 | // this.player.pause(); 191 | // })); 192 | // 193 | // this.player.addEvent("pause", _V_.proxy(this, function(){ 194 | // equal(this.player.paused(), true); 195 | // start(); 196 | // })); 197 | // 198 | // this.player.play(); 199 | // }); 200 | // 201 | // test("currentTime()", 1, function() { 202 | // stop(); 203 | // 204 | // // Try for 3 time updates, sometimes it updates at 0 seconds. 205 | // // var tries = 0; 206 | // 207 | // // Can't rely on just time update because it's faked for Flash. 208 | // this.player.one("loadeddata", _V_.proxy(this, function(){ 209 | // 210 | // this.player.addEvent("timeupdate", _V_.proxy(this, function(){ 211 | // 212 | // if (this.player.currentTime() > 0) { 213 | // ok(true, "Time is greater than 0."); 214 | // start(); 215 | // } else { 216 | // // tries++; 217 | // } 218 | // 219 | // // if (tries >= 3) { 220 | // // start(); 221 | // // } 222 | // })); 223 | // 224 | // })); 225 | // 226 | // this.player.play(); 227 | // }); 228 | // 229 | // 230 | // test("currentTime(seconds)", 2, function() { 231 | // stop(); 232 | // 233 | // // var afterPlayback = _V_.proxy(this, function(){ 234 | // // this.player.currentTime(this.player.duration() / 2); 235 | // // 236 | // // this.player.addEvent("timeupdate", _V_.proxy(this, function(){ 237 | // // ok(this.player.currentTime() > 0, "Time is greater than 0."); 238 | // // 239 | // // this.player.pause(); 240 | // // 241 | // // this.player.addEvent("timeupdate", _V_.proxy(this, function(){ 242 | // // ok(this.player.currentTime() == 0, "Time is 0."); 243 | // // start(); 244 | // // })); 245 | // // 246 | // // this.player.currentTime(0); 247 | // // })); 248 | // // }); 249 | // 250 | // // Wait for Source to be ready. 251 | // this.player.one("loadeddata", _V_.proxy(this, function(){ 252 | // 253 | // _V_.log("loadeddata", this.player); 254 | // this.player.currentTime(this.player.duration() - 1); 255 | // 256 | // })); 257 | // 258 | // this.player.one("seeked", _V_.proxy(this, function(){ 259 | // 260 | // _V_.log("seeked", this.player.currentTime()) 261 | // ok(this.player.currentTime() > 1, "Time is greater than 1."); 262 | // 263 | // this.player.one("seeked", _V_.proxy(this, function(){ 264 | // 265 | // _V_.log("seeked2", this.player.currentTime()) 266 | // 267 | // ok(this.player.currentTime() <= 1, "Time is less than 1."); 268 | // start(); 269 | // 270 | // })); 271 | // 272 | // this.player.currentTime(0); 273 | // 274 | // })); 275 | // 276 | // 277 | // this.player.play(); 278 | // 279 | // // this.player.one("timeupdate", _V_.proxy(this, function(){ 280 | // // 281 | // // this.player.currentTime(this.player.duration() / 2); 282 | // // 283 | // // this.player.one("timeupdate", _V_.proxy(this, function(){ 284 | // // ok(this.player.currentTime() > 0, "Time is greater than 0."); 285 | // // 286 | // // this.player.pause(); 287 | // // this.player.currentTime(0); 288 | // // 289 | // // this.player.one("timeupdate", _V_.proxy(this, function(){ 290 | // // 291 | // // ok(this.player.currentTime() == 0, "Time is 0."); 292 | // // start(); 293 | // // 294 | // // })); 295 | // // 296 | // // })); 297 | // // 298 | // // 299 | // // })); 300 | // 301 | // }); 302 | // 303 | // /* Events 304 | // ================================================================================ */ 305 | // module("API Events", { 306 | // setup: playerSetup, 307 | // teardown: playerTeardown 308 | // }); 309 | // 310 | // var playEventList = [] 311 | // 312 | // // Test all playback events 313 | // test("Initial Events", 11, function() { 314 | // stop(); // Give 30 seconds to run then fail. 315 | // 316 | // var events = [ 317 | // // "loadstart" // Called during setup 318 | // "play", 319 | // "playing", 320 | // 321 | // "durationchange", 322 | // "loadedmetadata", 323 | // "loadeddata", 324 | // 325 | // "progress", 326 | // "timeupdate", 327 | // 328 | // "canplay", 329 | // "canplaythrough", 330 | // 331 | // "pause", 332 | // "ended" 333 | // ]; 334 | // 335 | // // Add an event listener for each event type. 336 | // for (var i=0, l=events.length; i