├── .npmignore ├── .gitignore ├── src ├── ender.js └── audio5.js ├── swf └── audio5js.swf ├── flash ├── audio5js.fla └── audio5js.as ├── tests ├── assets │ └── sample.mp3 ├── tests_requirejs.html ├── functional │ ├── audio5_requirejs_spec.js │ ├── audio5_object_spec.js │ ├── audio5_playback_spec.js │ └── audio5_callbacks_spec.js ├── tests.html └── mocha.css ├── demos ├── angular │ ├── ngAudio5.js │ ├── ngAudio5Directive.js │ ├── ngAudio5Directive-example.html │ └── ngAudio5-example.html └── soundcloud │ ├── index.html │ └── main.js ├── component.json ├── make └── build.json ├── package.json ├── Gruntfile.js ├── audio5.min.js ├── README.md └── audio5.js /.npmignore: -------------------------------------------------------------------------------- 1 | examples/* 2 | flash/* 3 | make/* 4 | .idea/* 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | examples 2 | node_modules 3 | bower 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /src/ender.js: -------------------------------------------------------------------------------- 1 | ender.ender({ 2 | audio5: require('audio5') 3 | }); -------------------------------------------------------------------------------- /swf/audio5js.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zohararad/audio5js/HEAD/swf/audio5js.swf -------------------------------------------------------------------------------- /flash/audio5js.fla: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zohararad/audio5js/HEAD/flash/audio5js.fla -------------------------------------------------------------------------------- /tests/assets/sample.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zohararad/audio5js/HEAD/tests/assets/sample.mp3 -------------------------------------------------------------------------------- /demos/angular/ngAudio5.js: -------------------------------------------------------------------------------- 1 | angular.module('Audio5', []).factory('AudioService', function () { 2 | "use strict"; 3 | 4 | var params = { 5 | swf_path:'../../swf/audio5js.swf', 6 | throw_errors:true, 7 | format_time:true 8 | }; 9 | 10 | var audio5js = new Audio5js(params); 11 | 12 | return audio5js; 13 | }); 14 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "audio5", 3 | "description": "HTML5 Audio Compatibility Layer", 4 | "version": "0.1.9", 5 | "homepage": "http://zohararad.github.com/audio5js/", 6 | "keywords": [ 7 | "audio", 8 | "HTML5", 9 | "API" 10 | ], 11 | "author": "Zohar Arad (http://zohararad.com)", 12 | "contributors": [], 13 | "scripts": [ 14 | "audio5.js" 15 | ], 16 | "main": "audio5.js", 17 | "files": [ 18 | "swf/audio5js.swf" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/zohararad/audio5js.git" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /make/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "JAVASCRIPT": { 3 | "DIST_DIR": "./", 4 | "audio5": [ 5 | "./src/copyright.js", 6 | "./src/audio5.js" 7 | ] 8 | }, 9 | "JSHINT_OPTS": { 10 | "curly": true, 11 | "eqeqeq": true, 12 | "forin": true, 13 | "indent": 2, 14 | "latedef": true, 15 | "newcap": true, 16 | "regexp": true, 17 | "unused": true, 18 | "undef": true, 19 | "strict": true, 20 | "asi": false, 21 | "boss": false, 22 | "debug": false, 23 | "devel": false, 24 | "evil": false, 25 | "eqnull": false, 26 | "white": false, 27 | "sub": true, 28 | "shadow": false, 29 | "node": true, 30 | "browser": true, 31 | "wsh": true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/tests_requirejs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "audio5", 3 | "description": "HTML5 Audio Compatibility Layer", 4 | "version": "0.1.12", 5 | "homepage": "http://zohararad.github.com/audio5js/", 6 | "keywords": [ 7 | "audio", 8 | "HTML5", 9 | "API" 10 | ], 11 | "author": "Zohar Arad (http://zohararad.com)", 12 | "contributors": [], 13 | "main": "./audio5.js", 14 | "ender": "./src/ender.js", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/zohararad/audio5js.git" 18 | }, 19 | "devDependencies": { 20 | "grunt": "~0.4.0", 21 | "grunt-contrib-uglify": "~0.9.2", 22 | "grunt-contrib-jshint": "~0.1.0", 23 | "grunt-contrib-concat": "~0.1.2rc6", 24 | "grunt-bumpx": "~0.1.0", 25 | "grunt-exec": "0.4.2", 26 | "mocha": "1.8.1", 27 | "chai": "~1.5.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /demos/angular/ngAudio5Directive.js: -------------------------------------------------------------------------------- 1 | angular.module('Audio5').directive('audio', function(AudioService){ 2 | return { 3 | restrict: 'EA', 4 | scope: { 5 | 'src': '=source' 6 | }, 7 | template: '' + 8 | '
' + 9 | '' + 10 | '
position: {{position}}
' + 11 | '
duration: {{duration}}
'+ 12 | '
', 13 | replace: true, 14 | transclude: true, 15 | controller: function($scope, $element, $attrs, $transclude) { 16 | $scope.player = AudioService; 17 | 18 | $scope.player.on('timeupdate',function(time, duration){ 19 | $scope.$apply(function(){ 20 | $scope.position = time; 21 | $scope.duration = duration; 22 | }); 23 | }); 24 | 25 | $scope.$watch('src', function(new_value, old_value){ 26 | $scope.player.load(new_value); 27 | }); 28 | } 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /demos/soundcloud/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Play 10 last sounds from SoundCloud with Audio5JS. 6 | 28 | 29 | 30 |

Click on the tracks below to play 10 last sounds from SoundCloud with Audio5JS.

31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tests/functional/audio5_requirejs_spec.js: -------------------------------------------------------------------------------- 1 | require(['../../src/audio5'], function(Audio5js){ 2 | "use strict"; 3 | var audio5, player; 4 | describe("CommonJS", function(){ 5 | 6 | it('should not include global Audio5js object', function(){ 7 | expect(window.Audio5js).to.be.undefined; 8 | }); 9 | 10 | it('should be able to create a new instance', function(done){ 11 | audio5 = new Audio5js({ 12 | swf_path: '../swf/audio5js.swf', 13 | codecs: ['mp4', 'mp3'], 14 | ready: function (o) { 15 | player = o; 16 | this.should.be.an('object'); 17 | done(); 18 | this.destroy(); 19 | } 20 | }); 21 | }); 22 | 23 | it('should use the specified codec', function(){ 24 | if (Audio5js.can_play('mp4')){ 25 | player.codec.should.eql('mp4'); 26 | } else { //fallback is mp3 27 | player.codec.should.eql('mp3'); 28 | } 29 | }); 30 | 31 | }); 32 | 33 | mocha.run(); 34 | }); -------------------------------------------------------------------------------- /tests/tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /demos/angular/ngAudio5Directive-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 |
14 | Filename : Orb of Envisage.mp3
15 | © Music by Dan-O at DanoSongs.com 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/functional/audio5_object_spec.js: -------------------------------------------------------------------------------- 1 | describe('Audio5 Object', function(){ 2 | "use strict"; 3 | 4 | var audio5, player; 5 | 6 | it('should include global Audio5js object', function(){ 7 | window.Audio5js.should.be.a('function'); 8 | }); 9 | 10 | it('should be able to create a new instance', function(done){ 11 | audio5 = new Audio5js({ 12 | swf_path: '../swf/audio5js.swf', 13 | codecs: ['mp4', 'mp3'], 14 | ready: function (o) { 15 | player = o; 16 | expect(this).to.be.an('object'); 17 | this.destroy(); 18 | done(); 19 | } 20 | }); 21 | }); 22 | 23 | it('should use the specified codec', function(){ 24 | if (Audio5js.can_play('mp4')){ 25 | expect(player.codec).to.be.equal('mp4'); 26 | } else { //fallback is mp3 27 | expect(player.codec).to.be.equal('mp3'); 28 | } 29 | }); 30 | 31 | it('should destroy audio', function(done) { 32 | audio5 = new Audio5js({ 33 | swf_path: '../swf/audio5js.swf', 34 | codecs: ['mp4', 'mp3'], 35 | ready: function (o) { 36 | var that = this; 37 | this.on('canplay', function () { 38 | that.destroy(); 39 | expect(that.audio.audio).to.be.undefined; 40 | done(); 41 | }); 42 | this.load('./assets/sample.mp3'); 43 | } 44 | }); 45 | }); 46 | 47 | }); -------------------------------------------------------------------------------- /demos/soundcloud/main.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var container = document.querySelector('ul'), 3 | CLIENT_ID = '?client_id=YOUR_CLIENT_ID>'; 4 | 5 | var audio5js = new Audio5js({ 6 | swf_path:'../../swf/audio5js.swf', 7 | throw_errors: true, 8 | ready: function () { 9 | var player = this; 10 | 11 | SC.initialize({ 12 | client_id: "YOUR_CLIENT_ID" 13 | }); 14 | 15 | player.on('canplay', function(){ 16 | player.play(); 17 | }); 18 | 19 | // get last 10 tracks from SoundCloud and show them 20 | SC.get("/tracks", { limit: 10 }, function (tracks) { 21 | var html = ""; 22 | tracks.forEach(function (track) { 23 | html += [ 24 | '
  • ', track.title, '', 26 | '
  • ' 27 | ].join(''); 28 | }); 29 | container.innerHTML = html; 30 | }); 31 | 32 | container.addEventListener('click', function (e) { 33 | var track = e.target.parentNode; 34 | if (track.className.indexOf('track') !== -1) { 35 | var url = track.getAttribute('data-stream'); 36 | if(player.playing){ 37 | player.pause(); 38 | } 39 | player.load(url); 40 | } 41 | }); 42 | } 43 | }); 44 | 45 | }()); 46 | -------------------------------------------------------------------------------- /demos/angular/ngAudio5-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Position {{player.position}} 12 | Duration {{player.duration}} 13 | 14 |
    15 | Filename : Orb of Envisage.mp3
    16 | © Music by Dan-O at DanoSongs.com 17 |
    18 | 19 | 20 | 21 | 22 | 23 | 24 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // Project configuration. 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | uglify: { 7 | options: { 8 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' 9 | }, 10 | build: { 11 | src: ['./<%= pkg.name %>.js'], 12 | dest: './<%= pkg.name %>.min.js' 13 | } 14 | }, 15 | concat: { 16 | options: { 17 | separator: ';' 18 | }, 19 | dist: { 20 | src: ['./src/<%= pkg.name %>.js'], 21 | dest: './<%= pkg.name %>.js' 22 | } 23 | }, 24 | jshint: { 25 | files: ["./src/<%= pkg.name %>.js"], 26 | options: { 27 | "curly": true, 28 | "eqeqeq": true, 29 | "forin": true, 30 | "indent": 2, 31 | "latedef": true, 32 | "newcap": true, 33 | "regexp": true, 34 | "unused": true, 35 | "undef": true, 36 | "strict": true, 37 | "asi": false, 38 | "boss": false, 39 | "debug": false, 40 | "devel": false, 41 | "evil": false, 42 | "eqnull": false, 43 | "white": false, 44 | "sub": true, 45 | "shadow": false, 46 | "node": true, 47 | "browser": true, 48 | "wsh": true 49 | } 50 | }, 51 | bump: { 52 | options: {}, 53 | files: [ 'package.json', 'bower/component.json' ] 54 | }, 55 | exec: { 56 | copy_bower: { 57 | cmd: 'cp ./<%= pkg.name %>*.js ./bower/; cp ./swf/*.swf ./bower/' 58 | } 59 | } 60 | }); 61 | 62 | // Load the plugin that provides the "uglify" task. 63 | grunt.loadNpmTasks('grunt-contrib-uglify'); 64 | grunt.loadNpmTasks('grunt-contrib-jshint'); 65 | grunt.loadNpmTasks('grunt-contrib-concat'); 66 | grunt.loadNpmTasks('grunt-bumpx'); 67 | grunt.loadNpmTasks('grunt-exec'); 68 | 69 | // Default task(s). 70 | grunt.registerTask('default', ['jshint', 'concat', 'uglify']); 71 | grunt.registerTask('bower', ['jshint', 'concat', 'uglify', 'exec:copy_bower']); 72 | grunt.registerTask('release', ['jshint', 'concat', 'uglify', 'exec:copy_bower', 'bump::patch']); 73 | 74 | }; -------------------------------------------------------------------------------- /tests/functional/audio5_playback_spec.js: -------------------------------------------------------------------------------- 1 | describe('Audio5 Playback', function(){ 2 | "use strict"; 3 | 4 | var audio5; 5 | 6 | it('should toggle playPause', function(done){ 7 | audio5 = new Audio5js({ 8 | swf_path: '../swf/audio5js.swf', 9 | codecs: ['mp3'], 10 | ready: function () { 11 | var that = this; 12 | this.on('canplay', function () { 13 | that.playPause(); 14 | }); 15 | 16 | this.on('play', function(){ 17 | setTimeout(function(){ 18 | that.playPause(); 19 | }, 1000); 20 | }); 21 | 22 | this.on('pause', function(){ 23 | if(!that.playing){ 24 | done(); 25 | that.destroy(); 26 | } 27 | }); 28 | this.load('./assets/sample.mp3'); 29 | } 30 | }); 31 | }); 32 | it('should have a duration', function(done){ 33 | audio5 = new Audio5js({ 34 | swf_path: '../swf/audio5js.swf', 35 | codecs: ['mp3'], 36 | format_time: false, 37 | ready: function () { 38 | var that = this; 39 | 40 | this.on('timeupdate', function () { 41 | that.pause(); 42 | var d = Math.round(that.duration); 43 | expect(d).to.be.equal(4); 44 | done(); 45 | that.destroy(); 46 | }); 47 | 48 | this.load('./assets/sample.mp3'); 49 | this.play(); 50 | } 51 | }); 52 | }); 53 | 54 | it('should be seekable', function(done){ 55 | audio5 = new Audio5js({ 56 | swf_path: '../swf/audio5js.swf', 57 | codecs: ['mp3'], 58 | ready: function () { 59 | var that = this; 60 | 61 | this.on('canplay', function () { 62 | that.play(); 63 | setTimeout(function(){ 64 | that.pause(); 65 | expect(that.audio.seekable).to.be.equal(true); 66 | done(); 67 | that.destroy(); 68 | }, 2000); 69 | }); 70 | 71 | this.load('./assets/sample.mp3'); 72 | } 73 | }); 74 | }); 75 | 76 | it('should seek to a position', function(done){ 77 | audio5 = new Audio5js({ 78 | swf_path: '../swf/audio5js.swf', 79 | codecs: ['mp3'], 80 | ready: function () { 81 | var that = this; 82 | 83 | this.on('canplay', function () { 84 | that.pause(); 85 | that.seek(4); 86 | expect(that.position).to.be.equal(4); 87 | done(); 88 | that.destroy(); 89 | }); 90 | 91 | this.load('./assets/sample.mp3'); 92 | } 93 | }); 94 | }); 95 | 96 | it('should change volume', function(done){ 97 | audio5 = new Audio5js({ 98 | swf_path: '../swf/audio5js.swf', 99 | codecs: ['mp3'], 100 | ready: function () { 101 | var that = this; 102 | 103 | this.on('play', function () { 104 | setTimeout(function(){ 105 | that.pause(); 106 | that.volume(0); 107 | expect(that.volume()).to.be.equal(0); 108 | done(); 109 | that.destroy(); 110 | }, 1000); 111 | }); 112 | 113 | this.load('./assets/sample.mp3'); 114 | this.play(); 115 | } 116 | }); 117 | }); 118 | 119 | it('should change the rate', function(done){ 120 | audio5 = new Audio5js({ 121 | codecs: ['mp3'], 122 | ready: function () { 123 | var that = this; 124 | 125 | this.on('play', function () { 126 | setTimeout(function(){ 127 | expect(that.rate()).to.be.equal(0.5); 128 | that.rate(1.5); 129 | expect(that.rate()).to.be.equal(1.5); 130 | done(); 131 | that.destroy(); 132 | }, 1000); 133 | }); 134 | 135 | this.load('./assets/sample.mp3'); 136 | this.rate(0.5) 137 | this.play(); 138 | } 139 | }); 140 | }); 141 | }); -------------------------------------------------------------------------------- /tests/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 5 | padding: 60px 50px; 6 | } 7 | 8 | #mocha ul, #mocha li { 9 | margin: 0; 10 | padding: 0; 11 | } 12 | 13 | #mocha ul { 14 | list-style: none; 15 | } 16 | 17 | #mocha h1, #mocha h2 { 18 | margin: 0; 19 | } 20 | 21 | #mocha h1 { 22 | margin-top: 15px; 23 | font-size: 1em; 24 | font-weight: 200; 25 | } 26 | 27 | #mocha h1 a { 28 | text-decoration: none; 29 | color: inherit; 30 | } 31 | 32 | #mocha h1 a:hover { 33 | text-decoration: underline; 34 | } 35 | 36 | #mocha .suite .suite h1 { 37 | margin-top: 0; 38 | font-size: .8em; 39 | } 40 | 41 | .hidden { 42 | display: none; 43 | } 44 | 45 | #mocha h2 { 46 | font-size: 12px; 47 | font-weight: normal; 48 | cursor: pointer; 49 | } 50 | 51 | #mocha .suite { 52 | margin-left: 15px; 53 | } 54 | 55 | #mocha .test { 56 | margin-left: 15px; 57 | overflow: hidden; 58 | } 59 | 60 | #mocha .test.pending:hover h2::after { 61 | content: '(pending)'; 62 | font-family: arial; 63 | } 64 | 65 | #mocha .test.pass.medium .duration { 66 | background: #C09853; 67 | } 68 | 69 | #mocha .test.pass.slow .duration { 70 | background: #B94A48; 71 | } 72 | 73 | #mocha .test.pass::before { 74 | content: '✓'; 75 | font-size: 12px; 76 | display: block; 77 | float: left; 78 | margin-right: 5px; 79 | color: #00d6b2; 80 | } 81 | 82 | #mocha .test.pass .duration { 83 | font-size: 9px; 84 | margin-left: 5px; 85 | padding: 2px 5px; 86 | color: white; 87 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 88 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 89 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 90 | -webkit-border-radius: 5px; 91 | -moz-border-radius: 5px; 92 | -ms-border-radius: 5px; 93 | -o-border-radius: 5px; 94 | border-radius: 5px; 95 | } 96 | 97 | #mocha .test.pass.fast .duration { 98 | display: none; 99 | } 100 | 101 | #mocha .test.pending { 102 | color: #0b97c4; 103 | } 104 | 105 | #mocha .test.pending::before { 106 | content: '◦'; 107 | color: #0b97c4; 108 | } 109 | 110 | #mocha .test.fail { 111 | color: #c00; 112 | } 113 | 114 | #mocha .test.fail pre { 115 | color: black; 116 | } 117 | 118 | #mocha .test.fail::before { 119 | content: '✖'; 120 | font-size: 12px; 121 | display: block; 122 | float: left; 123 | margin-right: 5px; 124 | color: #c00; 125 | } 126 | 127 | #mocha .test pre.error { 128 | color: #c00; 129 | max-height: 300px; 130 | overflow: auto; 131 | } 132 | 133 | #mocha .test pre { 134 | display: block; 135 | float: left; 136 | clear: left; 137 | font: 12px/1.5 monaco, monospace; 138 | margin: 5px; 139 | padding: 15px; 140 | border: 1px solid #eee; 141 | border-bottom-color: #ddd; 142 | -webkit-border-radius: 3px; 143 | -webkit-box-shadow: 0 1px 3px #eee; 144 | -moz-border-radius: 3px; 145 | -moz-box-shadow: 0 1px 3px #eee; 146 | } 147 | 148 | #mocha .test h2 { 149 | position: relative; 150 | } 151 | 152 | #mocha .test a.replay { 153 | position: absolute; 154 | top: 3px; 155 | right: 0; 156 | text-decoration: none; 157 | vertical-align: middle; 158 | display: block; 159 | width: 15px; 160 | height: 15px; 161 | line-height: 15px; 162 | text-align: center; 163 | background: #eee; 164 | font-size: 15px; 165 | -moz-border-radius: 15px; 166 | border-radius: 15px; 167 | -webkit-transition: opacity 200ms; 168 | -moz-transition: opacity 200ms; 169 | transition: opacity 200ms; 170 | opacity: 0.3; 171 | color: #888; 172 | } 173 | 174 | #mocha .test:hover a.replay { 175 | opacity: 1; 176 | } 177 | 178 | #mocha-report.pass .test.fail { 179 | display: none; 180 | } 181 | 182 | #mocha-report.fail .test.pass { 183 | display: none; 184 | } 185 | 186 | #mocha-error { 187 | color: #c00; 188 | font-size: 1.5 em; 189 | font-weight: 100; 190 | letter-spacing: 1px; 191 | } 192 | 193 | #mocha-stats { 194 | position: fixed; 195 | top: 15px; 196 | right: 10px; 197 | font-size: 12px; 198 | margin: 0; 199 | color: #888; 200 | } 201 | 202 | #mocha-stats .progress { 203 | float: right; 204 | padding-top: 0; 205 | } 206 | 207 | #mocha-stats em { 208 | color: black; 209 | } 210 | 211 | #mocha-stats a { 212 | text-decoration: none; 213 | color: inherit; 214 | } 215 | 216 | #mocha-stats a:hover { 217 | border-bottom: 1px solid #eee; 218 | } 219 | 220 | #mocha-stats li { 221 | display: inline-block; 222 | margin: 0 5px; 223 | list-style: none; 224 | padding-top: 11px; 225 | } 226 | 227 | code .comment { color: #ddd } 228 | code .init { color: #2F6FAD } 229 | code .string { color: #5890AD } 230 | code .keyword { color: #8A6343 } 231 | code .number { color: #2F6FAD } 232 | -------------------------------------------------------------------------------- /flash/audio5js.as: -------------------------------------------------------------------------------- 1 | package { 2 | 3 | import flash.display.Sprite; 4 | import flash.errors.IOError; 5 | import flash.events.Event; 6 | import flash.events.IOErrorEvent; 7 | import flash.events.ProgressEvent; 8 | import flash.events.TimerEvent; 9 | import flash.external.ExternalInterface; 10 | import flash.media.ID3Info; 11 | import flash.media.Sound; 12 | import flash.media.SoundChannel; 13 | import flash.media.SoundTransform; 14 | import flash.net.URLRequest; 15 | import flash.system.Security; 16 | import flash.utils.Timer; 17 | 18 | public class audio5js extends Sprite { 19 | 20 | private var _channel:SoundChannel; 21 | private var sound:Sound; 22 | private var duration:Number; 23 | private var seekable:Boolean; 24 | private var playerInstance:String; 25 | private var pausePoint:Number = 0; 26 | private var playing:Boolean = false; 27 | private var volume:Number = 1; 28 | private var timer:Timer = new Timer(250, 0); 29 | 30 | private function get channel():SoundChannel { 31 | return this._channel; 32 | } 33 | 34 | private function set channel(channel:SoundChannel):void { 35 | this._channel = channel; 36 | this._channel.addEventListener(Event.SOUND_COMPLETE, this.soundEnded); 37 | } 38 | 39 | public function audio5js():void { 40 | Security.allowDomain("*"); 41 | Security.allowInsecureDomain("*"); 42 | 43 | // Wait until first frame to alert JS it's ready. 44 | addEventListener(Event.ENTER_FRAME, ready); 45 | } 46 | 47 | public function ready(event:Event):void 48 | { 49 | removeEventListener(Event.ENTER_FRAME, ready); 50 | var playerInstanceNumber:String = root.loaderInfo.parameters.playerInstanceNumber; 51 | 52 | if (isNaN(Number(playerInstanceNumber))) { 53 | return; 54 | } 55 | 56 | this.playerInstance = 'window.Audio5js_flash.instances[' + root.loaderInfo.parameters.playerInstanceNumber + '].'; 57 | 58 | ExternalInterface.addCallback('load', load); 59 | ExternalInterface.addCallback('playPause', playPause); 60 | ExternalInterface.addCallback('pplay', play); 61 | ExternalInterface.addCallback('ppause', pause); 62 | ExternalInterface.addCallback('seekTo', seekTo); 63 | ExternalInterface.addCallback('setVolume', setVolume); 64 | 65 | ExternalInterface.call(this.playerInstance+'eiReady'); 66 | } 67 | 68 | private function load(mp3:String):void { 69 | if (this.channel) this.channel.stop(); 70 | if (this.sound) { 71 | this.sound.removeEventListener(Event.OPEN, this.loadStart); 72 | this.sound.removeEventListener(IOErrorEvent.IO_ERROR, this.loadError); 73 | this.sound.removeEventListener(ProgressEvent.PROGRESS, this.loadProgress); 74 | this.sound.removeEventListener(Event.ID3, this.ID3Loaded); 75 | this.timer.removeEventListener(TimerEvent.TIMER, this.timeUpdate); 76 | this.sound.removeEventListener(ProgressEvent.PROGRESS, this.canPlay); 77 | } 78 | 79 | this.seekable = false; 80 | this.channel = new SoundChannel(); 81 | this.sound = new Sound(new URLRequest(mp3)); 82 | this.pausePoint = 0; 83 | 84 | this.sound.addEventListener(Event.OPEN, this.loadStart); 85 | this.sound.addEventListener(IOErrorEvent.IO_ERROR, this.loadError); 86 | this.sound.addEventListener(ProgressEvent.PROGRESS, this.loadProgress); 87 | this.sound.addEventListener(Event.ID3, this.ID3Loaded); 88 | this.timer.addEventListener(TimerEvent.TIMER, this.timeUpdate); 89 | this.sound.addEventListener(ProgressEvent.PROGRESS, this.canPlay); 90 | } 91 | 92 | private function play():void { 93 | this.channel = this.sound.play(this.pausePoint); 94 | this.setVolume(this.volume); 95 | this.playing = true; 96 | this.timer.start(); 97 | ExternalInterface.call(this.playerInstance+'eiPlay'); 98 | } 99 | 100 | private function pause():void { 101 | this.pausePoint = this.channel.position; 102 | this.channel.stop(); 103 | this.playing = false; 104 | this.timer.stop(); 105 | ExternalInterface.call(this.playerInstance+'eiPause'); 106 | } 107 | 108 | private function playPause():void { 109 | if (this.playing) { 110 | this.pause(); 111 | } else { 112 | this.play(); 113 | } 114 | } 115 | 116 | private function seekTo(position:Number):void { 117 | ExternalInterface.call(this.playerInstance+'eiSeeking'); 118 | this.channel.stop(); 119 | this.pausePoint = position * 1000; 120 | ExternalInterface.call(this.playerInstance+'eiSeeked'); 121 | if (this.playing) { 122 | this.channel = this.sound.play(this.pausePoint); 123 | this.setVolume(this.volume); 124 | } else { 125 | this.timeUpdate(); 126 | } 127 | } 128 | 129 | private function setVolume(vol:Number):void { 130 | this.volume = vol; 131 | var transform:SoundTransform = this.channel.soundTransform; 132 | if (vol < 0) vol = 0; 133 | if (vol > 1) vol = 1; 134 | transform.volume = vol; 135 | channel.soundTransform = transform; 136 | ExternalInterface.call(this.playerInstance+'eiVolumeChange'); 137 | } 138 | 139 | private function loadStart(e:Event):void { 140 | ExternalInterface.call(this.playerInstance+'eiLoadStart'); 141 | } 142 | 143 | private function loadError(e:IOErrorEvent):void { 144 | var msg:String = e.text; 145 | ExternalInterface.call(this.playerInstance+'eiLoadError', msg); 146 | } 147 | 148 | private function loadProgress(e:ProgressEvent):void { 149 | this.duration = (e.bytesTotal / (e.bytesLoaded / this.sound.length)); 150 | var loadPercent:Number = e.bytesLoaded / e.bytesTotal; 151 | this.seekable = e.bytesTotal > 0 && this.sound.length > 0; 152 | 153 | if (loadPercent > 1) loadPercent = 1; 154 | if (!this.seekable) loadPercent = 0; 155 | if (loadPercent >= 0 && this.seekable) { 156 | ExternalInterface.call(this.playerInstance+'eiProgress', loadPercent * 100, this.duration / 1000, this.seekable); 157 | } 158 | } 159 | 160 | private function ID3Loaded(e:Event):void{ 161 | ExternalInterface.call(this.playerInstance+'eiLoadedMetadata'); 162 | } 163 | 164 | private function timeUpdate(e:TimerEvent = null):void { 165 | var targetPosition:Number = e ? this.channel.position : this.pausePoint; 166 | ExternalInterface.call(this.playerInstance+'eiTimeUpdate', targetPosition / 1000, this.duration / 1000, this.seekable); 167 | } 168 | 169 | private function canPlay(e:Event):void { 170 | this.sound.removeEventListener(ProgressEvent.PROGRESS, this.canPlay); 171 | ExternalInterface.call(this.playerInstance+'eiCanPlay'); 172 | } 173 | 174 | private function soundEnded(e:Event):void { 175 | ExternalInterface.call(this.playerInstance+'eiEnded'); 176 | } 177 | 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /tests/functional/audio5_callbacks_spec.js: -------------------------------------------------------------------------------- 1 | describe('Audio5 Callbacks', function(){ 2 | "use strict"; 3 | 4 | var audio5; 5 | 6 | it('should reach canPlay event', function(done){ 7 | audio5 = new Audio5js({ 8 | swf_path: '../swf/audio5js.swf', 9 | codecs: ['mp3'], 10 | ready: function () { 11 | var that = this; 12 | this.on('canplay', function () { 13 | that.pause(); 14 | that.destroy(); 15 | done(); 16 | }); 17 | this.load('./assets/sample.mp3'); 18 | } 19 | }); 20 | }); 21 | 22 | it('should trigger loadstart event', function(done){ 23 | audio5 = new Audio5js({ 24 | swf_path: '../swf/audio5js.swf', 25 | codecs: ['mp3'], 26 | ready: function () { 27 | var that = this; 28 | this.on('loadstart', function () { 29 | done(); 30 | that.pause(); 31 | that.destroy(); 32 | }); 33 | this.load('./assets/sample.mp3'); 34 | } 35 | }); 36 | }); 37 | 38 | it('should start downloading audio', function(done){ 39 | audio5 = new Audio5js({ 40 | swf_path: '../swf/audio5js.swf', 41 | codecs: ['mp3'], 42 | ready: function () { 43 | var that = this; 44 | this.on('progress', function (pos, dur) { 45 | that.destroy(); 46 | done(); 47 | }); 48 | this.load('./assets/sample.mp3'); 49 | this.play(); 50 | this.pause(); 51 | } 52 | }); 53 | }); 54 | 55 | it('should trigger play event', function(done){ 56 | audio5 = new Audio5js({ 57 | swf_path: '../swf/audio5js.swf', 58 | codecs: ['mp3'], 59 | ready: function () { 60 | var that = this; 61 | this.on('play', function () { 62 | setTimeout(function(){ 63 | that.pause(); 64 | that.destroy(); 65 | done(); 66 | }, 500); 67 | }); 68 | this.load('./assets/sample.mp3'); 69 | this.play(); 70 | } 71 | }); 72 | }); 73 | 74 | it('should trigger pause event', function(done){ 75 | audio5 = new Audio5js({ 76 | swf_path: '../swf/audio5js.swf', 77 | codecs: ['mp3'], 78 | ready: function () { 79 | var that = this; 80 | this.on('play', function () { 81 | setTimeout(function(){ 82 | that.pause(); 83 | }, 500); 84 | }); 85 | 86 | this.on('pause', function () { 87 | that.destroy(); 88 | done(); 89 | }); 90 | this.load('./assets/sample.mp3'); 91 | that.play(); 92 | } 93 | }); 94 | }); 95 | 96 | it('should trigger timeupdate event', function(done){ 97 | audio5 = new Audio5js({ 98 | swf_path: '../swf/audio5js.swf', 99 | codecs: ['mp3'], 100 | ready: function () { 101 | var that = this; 102 | this.on('timeupdate', function () { 103 | that.pause(); 104 | that.destroy(); 105 | done(); 106 | }); 107 | this.load('./assets/sample.mp3'); 108 | this.play(); 109 | } 110 | }); 111 | }); 112 | 113 | it('should be able to remove triggers from an event trigger', function(done){ 114 | 115 | var timeUpdateFuncCalled = 0; 116 | 117 | var timeupdateFunc = function(position, duration) { 118 | timeUpdateFuncCalled++; 119 | if(timeUpdateFuncCalled === 1) { 120 | // on time update listener, we might wish to remove a listener. 121 | this.off('timeupdate', timeupdateFunc); 122 | } else { 123 | // force tests to fail 124 | expect(timeUpdateFuncCalled).to.be.equal(1); 125 | } 126 | }; 127 | 128 | audio5 = new Audio5js({ 129 | swf_path: '../swf/audio5js.swf', 130 | codecs: ['mp3'], 131 | ready: function () { 132 | var that = this; 133 | this.on('timeupdate', timeupdateFunc, this); 134 | this.load('./assets/sample.mp3'); 135 | this.play(); 136 | 137 | setTimeout(function() { 138 | that.pause(); 139 | that.destroy(); 140 | expect(timeUpdateFuncCalled).to.be.equal(1); 141 | done(); 142 | }.bind(this), 2500); 143 | } 144 | }); 145 | }); 146 | 147 | it('should trigger ended event', function(done){ 148 | audio5 = new Audio5js({ 149 | swf_path: '../swf/audio5js.swf', 150 | codecs: ['mp3'], 151 | ready: function () { 152 | var that = this; 153 | this.on('ended', function () { 154 | that.destroy(); 155 | done(); 156 | }); 157 | this.one('canplay', function () { 158 | that.seek(3); 159 | that.play(); 160 | }); 161 | this.load('./assets/sample.mp3'); 162 | } 163 | }); 164 | }); 165 | 166 | it('should trigger an event once', function(done){ 167 | audio5 = new Audio5js({ 168 | swf_path: '../swf/audio5js.swf', 169 | codecs: ['mp3'], 170 | ready: function () { 171 | var that = this; 172 | this.one('play', function () { 173 | that.pause(); 174 | }); 175 | this.on('pause', function () { 176 | that.play(); 177 | }); 178 | this.on('ended', function () { 179 | that.destroy(); 180 | done(); 181 | }); 182 | 183 | this.one('canplay', function () { 184 | that.seek(3); 185 | that.play(); 186 | }); 187 | this.load('./assets/sample.mp3'); 188 | } 189 | }); 190 | }); 191 | 192 | it('should trigger seeking event', function(done){ 193 | audio5 = new Audio5js({ 194 | swf_path: '../swf/audio5js.swf', 195 | codecs: ['mp3'], 196 | ready: function () { 197 | var that = this; 198 | this.on('seeking', function () { 199 | that.destroy(); 200 | done(); 201 | }); 202 | this.on('canplay', function () { 203 | that.pause(); 204 | that.seek(4); 205 | }); 206 | this.load('./assets/sample.mp3'); 207 | } 208 | }); 209 | }); 210 | 211 | it('should trigger seeked event', function(done){ 212 | audio5 = new Audio5js({ 213 | swf_path: '../swf/audio5js.swf', 214 | codecs: ['mp3'], 215 | ready: function () { 216 | var that = this; 217 | this.on('seeked', function () { 218 | that.destroy(); 219 | done(); 220 | }); 221 | this.on('canplay', function () { 222 | that.pause(); 223 | that.seek(4); 224 | }); 225 | this.load('./assets/sample.mp3'); 226 | } 227 | }); 228 | }); 229 | 230 | it('should load metadata', function(done){ 231 | audio5 = new Audio5js({ 232 | swf_path: '../swf/audio5js.swf', 233 | codecs: ['mp3'], 234 | ready: function () { 235 | var that = this; 236 | this.on('loadedmetadata', function () { 237 | that.destroy(); 238 | done(); 239 | }); 240 | this.load('./assets/sample.mp3'); 241 | } 242 | }); 243 | }); 244 | 245 | }); -------------------------------------------------------------------------------- /audio5.min.js: -------------------------------------------------------------------------------- 1 | /*! audio5 2018-03-13 */ 2 | !function(a,b,c){"use strict";"undefined"!=typeof module&&module.exports?module.exports=c(b,a):"function"==typeof define&&define.amd?define(function(){return c(b,a)}):a[b]=c(b,a)}(window,"Audio5js",function(a,b){"use strict";function c(a){this.message=a}function d(a){var b,c={};for(b in a)"object"==typeof a[b]?c[b]=d(a[b]):c[b]=a[b];return c}var e=b.ActiveXObject;c.prototype=new Error;var f=function(a,b){var c,e=d(b);for(c in e)e.hasOwnProperty(c)&&(a[c]=e[c]);return a},g=function(a,b){return f(a.prototype,b)},h={on:function(a,b,c){this.subscribe(a,b,c,!1)},one:function(a,b,c){this.subscribe(a,b,c,!0)},off:function(a,b){if(void 0!==this.channels[a]){var c,d;for(c=0,d=this.channels[a].length;c0;){var e=this.channels[a].shift();e.once||c.push(e),"function"==typeof e.fn&&d.push(e)}for(this.channels[a]=c;d.length>0;){var f=d.shift();f.fn.apply(f.ctx,b)}}}},i={flash_embed_code:function(b,c,d){var f=a+b,g='';return(e?'':'')+g},can_play:function(a){var b,c=document.createElement("audio");switch(a){case"mp3":b="audio/mpeg;";break;case"vorbis":b='audio/ogg; codecs="vorbis"';break;case"opus":b='audio/ogg; codecs="opus"';break;case"webm":b='audio/webm; codecs="vorbis"';break;case"mp4":b='audio/mp4; codecs="mp4a.40.5"';break;case"wav":b='audio/wav; codecs="1"'}if(void 0!==b){if("mp3"===a&&navigator.userAgent.match(/Android/i)&&navigator.userAgent.match(/Firefox/i))return!0;try{return!!c.canPlayType&&""!==c.canPlayType(b)}catch(d){return!1}}return!1},has_flash:function(){var a=!1;if(navigator.plugins&&navigator.plugins.length&&navigator.plugins["Shockwave Flash"])a=!0;else if(navigator.mimeTypes&&navigator.mimeTypes.length){var b=navigator.mimeTypes["application/x-shockwave-flash"];a=b&&b.enabledPlugin}else try{var c=new e("ShockwaveFlash.ShockwaveFlash");a="object"==typeof c}catch(d){}return a}(),embedFlash:function(c,d){var e=document.createElement("div");if(e.style.position="absolute",e.style.width="1px",e.style.height="1px",e.style.top="1px",document.body.appendChild(e),"object"==typeof b.swfobject){var f={playerInstance:"window."+a+"_flash.instances['"+d+"']"},g={allowscriptaccess:"always",wmode:"transparent"};e.innerHTML='
    ',swfobject.embedSWF(c+"?ts="+((new Date).getTime()+Math.random()),a+d,"1","1","9.0.0",null,f,g)}else{var h=(new Date).getTime()+Math.random();e.innerHTML=this.flash_embed_code(d,c,h)}return document.getElementById(d)},formatTime:function(a){var b=parseInt(a/3600,10)%24,c=parseInt(a/60,10)%60,d=parseInt(a%60,10),e=(c<10?"0"+c:c)+":"+(d<10?"0"+d:d);return b>0?(b<10?"0"+b:b)+":"+e:e}};i.use_flash=i.can_play("mp3");var j,k,l,m={playing:!1,vol:1,duration:0,position:0,load_percent:0,seekable:!1,ready:null},n=b[a+"_flash"]=b[a+"_flash"]||{instances:[]};k=function(){if(i.use_flash&&!i.has_flash)throw new Error("Flash Plugin Missing")},k.prototype={init:function(a){n.instances.push(this),this.id=n.instances.length-1,this.embed(a)},embed:function(a){i.embedFlash(a,this.id)},eiReady:function(){this.audio=document.getElementById(a+this.id),this.trigger("ready")},eiLoadStart:function(){this.trigger("loadstart")},eiLoadedMetadata:function(){this.trigger("loadedmetadata")},eiCanPlay:function(){this.trigger("canplay")},eiTimeUpdate:function(a,b,c){this.position=a,this.duration=b,this.seekable=c,this.trigger("timeupdate",a,this.seekable?b:null)},eiProgress:function(a,b,c){this.load_percent=a,this.duration=b,this.seekable=c,this.trigger("progress",a)},eiLoadError:function(a){this.trigger("error",a)},eiPlay:function(){this.playing=!0,this.trigger("play"),this.trigger("playing")},eiPause:function(){this.playing=!1,this.trigger("pause")},eiEnded:function(){this.pause(),this.trigger("ended")},eiSeeking:function(){this.trigger("seeking")},eiSeeked:function(){this.trigger("seeked")},reset:function(){this.seekable=!1,this.duration=0,this.position=0,this.load_percent=0},load:function(a){this.reset(),this.audio.load(a)},play:function(){this.audio.pplay()},pause:function(){this.audio.ppause()},volume:function(a){if(void 0===a||isNaN(parseInt(a,10)))return this.vol;this.audio.setVolume(a),this.vol=a},seek:function(a){try{this.audio.seekTo(a),this.position=a}catch(b){}},rate:function(){},destroyAudio:function(){this.audio&&(this.pause(),this.audio.parentNode.removeChild(this.audio),delete n.instances[this.id],n.instances.splice(this.id,1),delete this.audio)}},g(k,h),g(k,m),l=function(){},l.prototype={init:function(){this._rate=1,this.trigger("ready")},createAudio:function(){this.audio=new Audio,this.audio.autoplay=!1,this.audio.preload="auto",this.audio.autobuffer=!0,this.audio.playbackRate=this._rate,this.bindEvents()},destroyAudio:function(){if(this.audio){this.pause(),this.unbindEvents();try{this.audio.setAttribute("src","")}finally{delete this.audio}}},setupEventListeners:function(){this.listeners={loadstart:this.onLoadStart.bind(this),canplay:this.onLoad.bind(this),loadedmetadata:this.onLoadedMetadata.bind(this),play:this.onPlay.bind(this),playing:this.onPlaying.bind(this),pause:this.onPause.bind(this),ended:this.onEnded.bind(this),error:this.onError.bind(this),timeupdate:this.onTimeUpdate.bind(this),seeking:this.onSeeking.bind(this),seeked:this.onSeeked.bind(this)}},bindEvents:function(){void 0===this.listeners&&this.setupEventListeners(),this.audio.addEventListener("loadstart",this.listeners.loadstart,!1),this.audio.addEventListener("canplay",this.listeners.canplay,!1),this.audio.addEventListener("loadedmetadata",this.listeners.loadedmetadata,!1),this.audio.addEventListener("play",this.listeners.play,!1),this.audio.addEventListener("playing",this.listeners.playing,!1),this.audio.addEventListener("pause",this.listeners.pause,!1),this.audio.addEventListener("ended",this.listeners.ended,!1),this.audio.addEventListener("error",this.listeners.error,!1),this.audio.addEventListener("timeupdate",this.listeners.timeupdate,!1),this.audio.addEventListener("seeking",this.listeners.seeking,!1),this.audio.addEventListener("seeked",this.listeners.seeked,!1)},unbindEvents:function(){this.audio.removeEventListener("loadstart",this.listeners.loadstart),this.audio.removeEventListener("canplay",this.listeners.canplay),this.audio.removeEventListener("loadedmetadata",this.listeners.loadedmetadata),this.audio.removeEventListener("play",this.listeners.play),this.audio.removeEventListener("playing",this.listeners.playing),this.audio.removeEventListener("pause",this.listeners.pause),this.audio.removeEventListener("ended",this.listeners.ended),this.audio.removeEventListener("error",this.listeners.error),this.audio.removeEventListener("timeupdate",this.listeners.timeupdate),this.audio.removeEventListener("seeking",this.listeners.seeking),this.audio.removeEventListener("seeked",this.listeners.seeked)},onLoadStart:function(){this.trigger("loadstart")},onLoad:function(){if(!this.audio)return setTimeout(this.onLoad.bind(this),100);this.seekable=this.audio.seekable&&this.audio.seekable.length>0,this.seekable&&(this.timer=setInterval(this.onProgress.bind(this),250)),this.trigger("canplay")},onLoadedMetadata:function(){this.trigger("loadedmetadata")},onPlay:function(){this.playing=!0,this.trigger("play")},onPlaying:function(){this.playing=!0,this.trigger("playing")},onPause:function(){this.playing=!1,this.trigger("pause")},onEnded:function(){this.playing=!1,this.trigger("ended")},onTimeUpdate:function(){if(this.audio&&this.playing){try{this.position=this.audio.currentTime,this.duration=this.audio.duration===1/0?null:this.audio.duration}catch(a){}this.trigger("timeupdate",this.position,this.duration)}},onProgress:function(){this.audio&&null!==this.audio.buffered&&this.audio.buffered.length&&(this.duration=this.audio.duration===1/0?null:this.audio.duration,this.load_percent=parseInt(this.audio.buffered.end(this.audio.buffered.length-1)/this.duration*100,10),this.trigger("progress",this.load_percent),this.load_percent>=100&&this.clearLoadProgress())},onError:function(a){this.trigger("error",a)},onSeeking:function(){this.trigger("seeking")},onSeeked:function(){this.trigger("seeked")},clearLoadProgress:function(){void 0!==this.timer&&(clearInterval(this.timer),delete this.timer)},reset:function(){this.clearLoadProgress(),this.seekable=!1,this.duration=0,this.position=0,this.load_percent=0},load:function(a){this.reset(),this.trigger("pause"),void 0===this.audio&&this.createAudio(),this.audio.setAttribute("src",a),this.audio.load()},play:function(){if(this.audio){var a=this.audio.play();return this.audio.playbackRate=this._rate,a}},pause:function(){this.audio&&this.audio.pause()},volume:function(a){if(void 0===a||isNaN(parseInt(a,10)))return this.vol;var b=a<0?0:Math.min(1,a);this.audio.volume=b,this.vol=b},seek:function(a){var b=this.playing;this.position=a,this.audio.currentTime=a,b?this.play():null!==this.audio.buffered&&this.audio.buffered.length&&this.trigger("timeupdate",this.position,this.duration)},rate:function(a){if(void 0===a||isNaN(parseFloat(a)))return this._rate;this._rate=a,this.audio&&(this.audio.playbackRate=a)}},g(l,h),g(l,m);var o={swf_path:"/swf/audiojs.swf",throw_errors:!0,format_time:!0,codecs:["mp3"]};return j=function(a){a=a||{};var b;for(b in o)o.hasOwnProperty(b)&&!a.hasOwnProperty(b)&&(a[b]=o[b]);this.init(a)},j.can_play=function(a){return i.can_play(a)},j.prototype={init:function(a){this.ready=!1,this.settings=a,this.audio=this.getPlayer(),this.bindAudioEvents(),this.settings.use_flash?this.audio.init(a.swf_path):this.audio.init()},getPlayer:function(){var a,b,c,d;if(this.settings.use_flash)c=new k,this.settings.player={engine:"flash",codec:"mp3"};else{for(a=0,b=this.settings.codecs.length;a` tags in your DOM, but instead lets you programmatically control every aspect of the audio playback cycle from Javascript. 13 | * **No UI** - Each player is different, and therefore the visual and functional implementation is left to you. Audio5js aims to facilitate the authoring of your audio player UI by exposing a unified API, and nothing more. 14 | * **No fluffy penguin abuse** - Audio5js will never abuse or hurt fluffy penguins wherever they might be. 15 | 16 | ## Getting Started 17 | 18 | Audio5js requires two components to work - the Javascript library `audio5.js` (or the minified version `audio5.min.js`) and the SWF fallback, found in `swf/audio5js.swf`. 19 | 20 | Simply download the source code, extract, and place both files somewhere in your project. For the purpose of demonstration, 21 | let's assume your project directory structure looks like this: 22 | 23 | ``` 24 | / 25 | -/public 26 | --/js 27 | --- audio5.js 28 | --/swf 29 | --- audio5js.swf 30 | ``` 31 | 32 | Now, you can include the Javascript in your HTML, and instantiate the Audio player: 33 | 34 | ```html 35 | 36 | 47 | ``` 48 | 49 | ## Configuration 50 | 51 | The Audio5js object accepts a configuration object with the following settings: 52 | 53 | * **swf_path** - The relative path to the MP3 Flash fallback SWF. Defaults to `/swf/audio5js.swf`. 54 | * **codecs** - Array of audio codecs to the player should try and play. Used to initialize the internal audio player based on codec support. Defaults to `['mp3']`. 55 | * **throw_errors** - A boolean flag indicating whether the player throws errors, or triggers an error event. Defaults to `true`. 56 | * **format_time** - A boolean flag indicating whether playback position and duration should be formatted to a time-string (hh:mm:ss), or remain as unformatted numbers (measured in seconds). Defaults to `true`. 57 | * **ready** - An optional callback that will be called when the player is ready to load and play audio. Called with an object containing player engine (html/flash) and supported codec as argument. 58 | 59 | Here's an example configuration using all the settings options above: 60 | 61 | ```html 62 | 83 | ``` 84 | 85 | ## API 86 | 87 | Audio5js exposes the following API: 88 | 89 | ### Instance Methods 90 | 91 | * **load** - load an audio file from URL 92 | * **play** - play loaded audio 93 | * **pause** - pause loaded audio 94 | * **playPause** - toggle play/pause playback state 95 | * **volume** - get / set volume (volume range is 0-1) 96 | * **seek** - move playhead position to a given time in seconds 97 | * **destroy** - destroys your Audio5js instance. Use to completely remove audio from the DOM and unbind all event listeners. 98 | * **rate** - set the playback rate. (playback rate range is 0.5-4.0) 99 | 100 | ### Instance Attributes 101 | 102 | * **playing** - boolean flag indicating whether audio is playing (true) or paused (false). 103 | * **duration** - audio duration in seconds. 104 | * **position** - audio playhead position in seconds. 105 | * **load_percent** - audio file download percentage (ranges 0 - 100). 106 | * **seekable** - audio is seekable (download) or not (streaming). 107 | 108 | ### Class Methods 109 | 110 | * **can_play** - Utility method to check whether the browser supports a certain audio mime-types. 111 | 112 | `Audio5js.can_play` class method supports the following mime-type queries: 113 | 114 | * **mp3** - check for `audio/mpeg; codecs="mp3"`. Example - `Audio5js.can_play('mp3')` 115 | * **vorbis** - check for `audio/ogg; codecs="vorbis"`. Example - `Audio5js.can_play('vorbis')` 116 | * **opus** - check for `audio/ogg; codecs="opus"`. Example - `Audio5js.can_play('opus')` 117 | * **webm** - check for `audio/webm; codecs="vorbis"`. Example - `Audio5js.can_play('webm')` 118 | * **mp4** - check for `audio/mp4; codecs="mp4a.40.5"`. Example - `Audio5js.can_play('mp4')` 119 | * **wav** - check for `audio/wav; codecs="1"`. Example - `Audio5js.can_play('wav')` 120 | 121 | ### API Example 122 | 123 | ```html 124 | 125 | 126 | 164 | ``` 165 | 166 | ## Events 167 | 168 | Like HTML5's Audio, Audio5js exposes events that can be used to capture the state and properties of the audio playback cycle: 169 | 170 | * **canplay** - triggered when the audio has been loaded can can be played. Analogue to HTML5 Audio `canplay` event. Note that Firefox will trigger this event after seeking as well - If you're listening to this event, we recommend you use the `one('canplay',callback)` event listener binding, instead of the `on('canplay',callback)`. 171 | * **play** - triggered when the audio begins playing. Analogue to HTML5 Audio `play` event. 172 | * **pause** - triggered when the audio is paused. Analogue to HTML5 Audio `pause` event. 173 | * **ended** - triggered when the audio playback has ended. Analogue to HTML5 Audio `ended` event. 174 | * **error** - triggered when the audio load error occurred. Analogue to HTML5 Audio `error` event. 175 | * **timeupdate** - triggered when the audio playhead position changes (during playback). Analogue to HTML5 Audio `timeupdate` event. 176 | * **progress** - triggered while audio file is being downloaded by the browser. Analogue to HTML5 Audio `progress` event. 177 | * **seeking** - audio is seeking to a new position (in seconds) 178 | * **seeked** - audio has been seeked successfully to new position 179 | * **loadedmetadata** - MP3 meta-data has been loaded (works with MP3 files only) 180 | 181 | ### Using Events 182 | 183 | To subscribe to an event triggered by Audio5js, you can use the `on` method. Similarly, to unsubscribe from an event, you can use the `off` method. 184 | 185 | The `on` method accepts the following arguments: 186 | 187 | * **event** - name of event to subscribe to 188 | * **callback** - callback function to execute when the event is triggered 189 | * **context** - execution context of callback function (reference to `this` inside the callback) 190 | 191 | The `off` method accepts the following arguments: 192 | 193 | * **event** - name of event to unsubscribe from 194 | * **callback** - the callback function passed during the event subscription 195 | 196 | ```javascript 197 | 198 | var audio5js = new Audio5js({ 199 | ready: audioReady 200 | }); 201 | 202 | var audioReady = function () { 203 | //this points to the Audio5js instance 204 | this.on('play', function () { console.log('play'); }, this); 205 | this.on('pause', function () { console.log('pause'); }, this); 206 | this.on('ended', function () { console.log('ended'); }, this); 207 | 208 | // timeupdate event passes audio duration and position to callback 209 | this.on('timeupdate', function (position, duration) { 210 | console.log(position, duration); 211 | }, this); 212 | 213 | // progress event passes load_percent to callback 214 | this.on('progress', function (load_percent) { 215 | console.log(load_percent); 216 | }, this); 217 | 218 | //error event passes error object to callback 219 | this.on('error', function (error) { 220 | console.log(error.message); 221 | }, this); 222 | } 223 | ``` 224 | 225 | ## Fallbacks and multiple audio sources 226 | 227 | Browser-based audio isn't perfect, and it's more than likely that you'll need to serve the same audio in two formats, to support 228 | a wider crowd. If you intend to play different audio sources, based on browser codec support, pass a list of desired codecs to the 229 | `codecs` array of the settings object. Note that passed codecs should be listed in order or precedence and 230 | that 'mp3' is always the fallback codec in case no other codec is supported by the browser. 231 | 232 | Here's an example of initializing Audio5js with multiple audio sources, based on browser support: 233 | 234 | ```javascript 235 | 236 | var audio5js = new Audio5js({ 237 | swf_path: '/swf/audio5js.swf', 238 | codecs: ['mp4', 'vorbis', 'mp3'], 239 | ready: function(player) { 240 | var audio_url; 241 | switch (player.codec) { 242 | case 'mp4': 243 | audio_url = '/audio/song.mp4'; 244 | break; 245 | case 'vorbis': 246 | audio_url = '/audio/song.ogg'; 247 | break; 248 | default: 249 | audio_url = '/audio/song.mp3'; 250 | break; 251 | } 252 | this.load(audio_url); 253 | this.play(); 254 | } 255 | }); 256 | 257 | ``` 258 | 259 | ## Safari Mobile 260 | 261 | Safari mobile won't let you play audio without explicit user interaction. In other words, the initial click on your "play" button 262 | needs to load the audio. Here's an example of how to load and play audio on Safari Mobile with Audio5js: 263 | 264 | ```html 265 | 266 | 289 | ``` 290 | 291 | ## AMD / RequireJS 292 | 293 | Audio5js comes baked with AMD / RequireJS support. Assuming your public directory structure is as above, here's an example of 294 | how to use Audio5js with RequireJS: 295 | 296 | Your HTML should look something like this: 297 | 298 | ```html 299 | 300 | ``` 301 | 302 | Inside `js/player.js` you can now require Audio5js like so: 303 | 304 | ```javascript 305 | require(["js/audio5"], function (Audio5js) { 306 | var audio5 = new Audio5js({ 307 | ready: function () { 308 | this.load('/somesong.mp3'); 309 | this.play(); 310 | } 311 | }); 312 | } ); 313 | ``` 314 | 315 | ## Ender.js 316 | 317 | Audio5js can also be used with Ender.js. Here's how you can add it to your project: 318 | 319 | ```bash 320 | # add audio5 as dependency 321 | $ ender build audio5 322 | ``` 323 | 324 | ```javascript 325 | //use as a global package 326 | var Audio5js = require('audio5'); 327 | var player = new Audio5js({ 328 | swf_path: 'node_modules/audio5/swf/audio5js.swf' 329 | }); 330 | 331 | // or via the Ender $ object 332 | var play = new $.audio5({ 333 | swf_path: 'node_modules/audio5/swf/audio5js.swf' 334 | }); 335 | ``` 336 | 337 | ## Angular.js 338 | 339 | Audio5js is available as an Angular.js service. 340 | 341 | ```html``` 342 | 343 | 344 | ``` 345 | 346 | ```javascript``` 347 | //inject Audio5 service into our app 348 | var app = angular.module('myapp',['Audio5']); 349 | 350 | //Inject the AudioService singleton into our controller 351 | var PlayCtrl = function ($scope, AudioService) { 352 | //bind AudioService to scope 353 | $scope.player = AudioService; 354 | //Load the song, every event, class method and Instance attribute from audio5js are accessible from the template 355 | $scope.player.load('http://danosongs.com/music/danosongs.com-orb-of-envisage.mp3'); 356 | 357 | //example of event binding 358 | $scope.player.on('progress',function(){ 359 | $scope.$apply(); 360 | }) 361 | } 362 | ``` 363 | 364 | ## Bower Support 365 | 366 | Audio5js is available as a Bower package. 367 | 368 | ``` 369 | $ bower install audio5js 370 | ``` 371 | 372 | ```html 373 | 374 | 379 | ``` 380 | 381 | ## Browser Support 382 | 383 | Audio5js doesn't try to please everyone. Having said that, it has been successfully tested on: 384 | 385 | * IE8, IE9 386 | * Chrome 23 (Mac) 387 | * Firefox 17 (Mac) 388 | * Safari 6 389 | * Opera 12 (Mac) 390 | * Safari Mobile (iOS 6.0) 391 | * Webkit Mobile (Android 4.0.4) 392 | 393 | ## TODO 394 | 395 | * Test on mobile browsers (Android). 396 | 397 | ## Contributing 398 | 399 | * Thanks to [Alex Wolkov](https://github.com/altryne) for AngularJS Demo 400 | * Thanks to [Yehonatan Daniv](https://github.com/ydaniv) for AMD/RequireJS support 401 | 402 | Please feel free to fork, fix and send me pull requests. Alternatively, open tickets for bugs and feature requests. 403 | 404 | ## Credits 405 | 406 | Audio5js relies heavily on the wonderful [audiojs library](http://kolber.github.com/audiojs/). The AS3 code for the fallback MP3 407 | player is taken almost as-is from audiojs, with some minor modifications for more comprehensive event handling. 408 | 409 | ## License 410 | 411 | Audio5js is released under the MIT License. 412 | 413 | Copyright (c) 2013 Zohar Arad 414 | 415 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 416 | 417 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 418 | 419 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 420 | 421 | ## Disclaimer 422 | 423 | No fluffy penguins were harmed during the making of Audio5js. 424 | -------------------------------------------------------------------------------- /audio5.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Audio5js: HTML5 Audio Compatibility Layer 3 | * https://github.com/zohararad/audio5js 4 | * License MIT (c) Zohar Arad 2013 5 | */ 6 | (function ($win, ns, factory) { 7 | "use strict"; 8 | /*global define */ 9 | /*global swfobject */ 10 | 11 | if (typeof (module) !== 'undefined' && module.exports) { // CommonJS 12 | module.exports = factory(ns, $win); 13 | } else if (typeof (define) === 'function' && define.amd) { // AMD 14 | define(function () { 15 | return factory(ns, $win); 16 | }); 17 | } else { //