├── src ├── fx │ ├── export.js │ ├── volume │ │ ├── export.js │ │ └── volume-lib.js │ └── equalizer │ │ ├── default.bands.js │ │ ├── export.js │ │ ├── equalizer-static.js │ │ ├── equalizer-band.js │ │ ├── default.presets.js │ │ └── equalizer.js ├── flash │ ├── build │ │ └── player-2_1.swf │ ├── src │ │ ├── AudioEvent.as │ │ ├── AudioLoader.as │ │ └── AudioManager.as │ ├── loader.js │ ├── flashembedder.js │ └── flash-interface.js ├── logger │ ├── export.js │ └── logger.js ├── lib │ ├── net │ │ └── error │ │ │ ├── export.js │ │ │ └── loader-error.js │ ├── noop.js │ ├── class │ │ ├── pure-instance.js │ │ ├── error-class.js │ │ └── proxy.js │ ├── async │ │ ├── reject.js │ │ ├── deferred.js │ │ ├── promise.js │ │ └── events.js │ ├── export.js │ ├── data │ │ └── merge.js │ └── browser │ │ ├── detect.js │ │ └── audioContextMonkeyPatch.js ├── error │ ├── export.js │ ├── playback-error.js │ └── audio-error.js ├── index.js ├── export.js ├── audio-static.js ├── config.js └── IAudioImplementation.jsdoc ├── dist └── player-2_1.swf ├── tutorial ├── images │ ├── alias.png │ ├── demo.png │ ├── fon.png │ ├── notch.png │ ├── sin.png │ ├── allpass.png │ ├── lowpass.png │ ├── peaking.png │ ├── spectro.jpg │ ├── bandpass.png │ ├── clipping.png │ ├── highpass.png │ ├── highshelf.png │ ├── lowshelf.png │ ├── bandpass-filter.png │ ├── bandstop-filter.png │ ├── discrete-signal.png │ ├── highpass-filter.png │ ├── lowpass-filter.png │ └── quantized-signal.png ├── contrib.md ├── quick-start.md ├── corner-case.md └── fx.md ├── jsdoc ├── doc │ ├── gfm-files │ │ ├── exported.hbs │ │ ├── event.hbs │ │ ├── typedef.hbs │ │ ├── parents-line.hbs │ │ ├── type.hbs │ │ ├── export-tree.hbs │ │ ├── member.hbs │ │ ├── namespace.hbs │ │ ├── index.hbs │ │ ├── property-table.hbs │ │ ├── layout.hbs │ │ ├── params-table.hbs │ │ ├── class.hbs │ │ ├── function.hbs │ │ └── children.hbs │ ├── gfm-single │ │ ├── exported.hbs │ │ ├── event.hbs │ │ ├── parents-line.hbs │ │ ├── type.hbs │ │ ├── typedef.hbs │ │ ├── export-tree.hbs │ │ ├── member.hbs │ │ ├── namespace.hbs │ │ ├── property-table.hbs │ │ ├── children.hbs │ │ ├── params-table.hbs │ │ ├── function.hbs │ │ ├── layout.hbs │ │ └── class.hbs │ ├── jsdoc │ │ ├── method.hbs │ │ ├── fullname.hbs │ │ ├── type.hbs │ │ ├── event.hbs │ │ ├── export-tree.hbs │ │ ├── params-table.hbs │ │ ├── property-table.hbs │ │ ├── member.hbs │ │ ├── namespace.hbs │ │ ├── typedef.hbs │ │ ├── function.hbs │ │ ├── class.hbs │ │ ├── children.hbs │ │ └── layout.hbs │ ├── jsdoc-tech │ │ ├── event.hbs │ │ ├── type.hbs │ │ ├── typedef.hbs │ │ ├── params-table.hbs │ │ ├── property-table.hbs │ │ ├── member.hbs │ │ ├── function.hbs │ │ ├── class.hbs │ │ ├── children.hbs │ │ ├── layout.hbs │ │ └── namespace.hbs │ ├── render.gfm.js │ ├── render.jsdoc.js │ ├── render.js │ └── publish.js └── jsdoc.public.json ├── AUTHORS ├── .gitignore ├── spec ├── LoaderError.md ├── readme.md ├── Deferred.md ├── ErrorClass.md ├── global.md ├── info.md ├── PlaybackError.md ├── Promise.md ├── AudioError.md ├── Logger.md ├── AbortablePromise.md ├── volumeLib.md ├── config.md ├── Events.md └── Equalizer.md ├── package.json ├── examples └── quick-start │ ├── index.html │ ├── index.css │ └── index.js ├── test └── web-audio-api │ ├── index.html │ └── index.js ├── Makefile ├── Gruntfile.js └── readme.md /src/fx/export.js: -------------------------------------------------------------------------------- 1 | require('../export'); 2 | 3 | ya.music.Audio.fx = {}; 4 | -------------------------------------------------------------------------------- /dist/player-2_1.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/dist/player-2_1.swf -------------------------------------------------------------------------------- /tutorial/images/alias.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/alias.png -------------------------------------------------------------------------------- /tutorial/images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/demo.png -------------------------------------------------------------------------------- /tutorial/images/fon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/fon.png -------------------------------------------------------------------------------- /tutorial/images/notch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/notch.png -------------------------------------------------------------------------------- /tutorial/images/sin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/sin.png -------------------------------------------------------------------------------- /jsdoc/doc/gfm-files/exported.hbs: -------------------------------------------------------------------------------- 1 | {{#if exported}} 2 | **Доступен извне как:** `{{exported}}` 3 | {{/if}} 4 | -------------------------------------------------------------------------------- /tutorial/images/allpass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/allpass.png -------------------------------------------------------------------------------- /tutorial/images/lowpass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/lowpass.png -------------------------------------------------------------------------------- /tutorial/images/peaking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/peaking.png -------------------------------------------------------------------------------- /tutorial/images/spectro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/spectro.jpg -------------------------------------------------------------------------------- /jsdoc/doc/gfm-single/exported.hbs: -------------------------------------------------------------------------------- 1 | {{#if exported}} 2 | **Доступен извне как:** `{{exported}}` 3 | {{/if}} 4 | -------------------------------------------------------------------------------- /src/flash/build/player-2_1.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/src/flash/build/player-2_1.swf -------------------------------------------------------------------------------- /src/fx/volume/export.js: -------------------------------------------------------------------------------- 1 | require('../export'); 2 | 3 | ya.music.Audio.fx.volumeLib = require('./volume-lib'); 4 | -------------------------------------------------------------------------------- /tutorial/images/bandpass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/bandpass.png -------------------------------------------------------------------------------- /tutorial/images/clipping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/clipping.png -------------------------------------------------------------------------------- /tutorial/images/highpass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/highpass.png -------------------------------------------------------------------------------- /tutorial/images/highshelf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/highshelf.png -------------------------------------------------------------------------------- /tutorial/images/lowshelf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/lowshelf.png -------------------------------------------------------------------------------- /src/fx/equalizer/default.bands.js: -------------------------------------------------------------------------------- 1 | module.exports = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000]; 2 | -------------------------------------------------------------------------------- /src/fx/equalizer/export.js: -------------------------------------------------------------------------------- 1 | require('../export'); 2 | 3 | ya.music.Audio.fx.Equalizer = require('./equalizer'); 4 | -------------------------------------------------------------------------------- /tutorial/images/bandpass-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/bandpass-filter.png -------------------------------------------------------------------------------- /tutorial/images/bandstop-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/bandstop-filter.png -------------------------------------------------------------------------------- /tutorial/images/discrete-signal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/discrete-signal.png -------------------------------------------------------------------------------- /tutorial/images/highpass-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/highpass-filter.png -------------------------------------------------------------------------------- /tutorial/images/lowpass-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/lowpass-filter.png -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc/method.hbs: -------------------------------------------------------------------------------- 1 | "{{> fullname}}": function({{#each params}}{{#unless @first}}, {{/unless}}{{{name}}}{{/each}}){}, 2 | -------------------------------------------------------------------------------- /tutorial/images/quantized-signal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/audio-js/master/tutorial/images/quantized-signal.png -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc/fullname.hbs: -------------------------------------------------------------------------------- 1 | {{#if parent}}{{{parent.longname}}}{{#if static}}.{{else inner}}~{{else}}#{{/if}}{{/if}}{{{name}}} 2 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc/type.hbs: -------------------------------------------------------------------------------- 1 | {{#if type.names}}\{ {{#each type.names}}{{#unless @first}} | {{/unless}}{{{this}}}{{/each}} \}{{/if}} 2 | -------------------------------------------------------------------------------- /src/logger/export.js: -------------------------------------------------------------------------------- 1 | require("../export"); 2 | 3 | var Logger = require('./logger'); 4 | 5 | ya.music.Audio.Logger = Logger; 6 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-files/event.hbs: -------------------------------------------------------------------------------- 1 | #### *event* {{{longname}}} 2 | 3 | {{{description}}} 4 | 5 | {{> params-table}} 6 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-single/event.hbs: -------------------------------------------------------------------------------- 1 | #### *event* {{{longname}}} 2 | 3 | {{{description}}} 4 | 5 | {{> params-table}} 6 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-files/typedef.hbs: -------------------------------------------------------------------------------- 1 | #### *type* {{{name}}} {{> type line=1}} 2 | 3 | {{{description}}} 4 | 5 | {{> property-table}} 6 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-files/parents-line.hbs: -------------------------------------------------------------------------------- 1 | {{#if parent}}{{#if parent.parent}}{{> parents-line parent}}{{/if}}{{parent.name}}{{#if static}}.{{else}}#{{/if}}{{/if}} 2 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-files/type.hbs: -------------------------------------------------------------------------------- 1 | {{#if type.names}}{{#if line}}: {{/if}}{{#each type.names}}{{#unless @first}} \| {{/unless}}\{@link {{this}}\}{{/each}}{{/if}} 2 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-single/parents-line.hbs: -------------------------------------------------------------------------------- 1 | {{#if parent}}{{#if parent.parent}}{{> parents-line parent}}{{/if}}{{parent.name}}{{#if static}}.{{else}}#{{/if}}{{/if}} 2 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-single/type.hbs: -------------------------------------------------------------------------------- 1 | {{#if type.names}}{{#if line}}: {{/if}}{{#each type.names}}{{#unless @first}} \| {{/unless}}\{@link {{this}}\}{{/each}}{{/if}} 2 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-single/typedef.hbs: -------------------------------------------------------------------------------- 1 | #### *type* {{{name}}} {{> type line=1}} 2 | 3 | {{{description}}} 4 | 5 | {{> property-table}} 6 | -------------------------------------------------------------------------------- /src/lib/net/error/export.js: -------------------------------------------------------------------------------- 1 | require('../../../export'); 2 | 3 | var LoaderError = require('./loader-error'); 4 | 5 | ya.music.Audio.LoaderError = LoaderError; 6 | -------------------------------------------------------------------------------- /src/lib/noop.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Заглушка в виде пустой функции на все случаи жизни 3 | * @private 4 | */ 5 | var noop = function() {}; 6 | 7 | module.exports = noop; 8 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-files/export-tree.hbs: -------------------------------------------------------------------------------- 1 | {{#each sub}} 2 | {{../indent}}* {{#if link}}\{@link {{link}} {{@key}}\}{{else}}{{@key}}{{/if}} 3 | {{> export-tree}} 4 | {{/each}} 5 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-single/export-tree.hbs: -------------------------------------------------------------------------------- 1 | {{#each sub}} 2 | {{../indent}}* {{#if link}}\{@link {{link}} {{@key}}\}{{else}}{{@key}}{{/if}} 3 | {{> export-tree}} 4 | {{/each}} 5 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | The following authors have created the source code of "YandexAudio" published and distributed by YANDEX LLC as the owner: 2 | 3 | Silaev Mikhail 4 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc-tech/event.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * {{{description}}} 3 | * {{#if type.names}}@type {{> type}}{{/if}} 4 | * @event 5 | * @name {{{longname}}} 6 | {{> params-table}} 7 | */ 8 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc-tech/type.hbs: -------------------------------------------------------------------------------- 1 | {{#if type.names}}{{#unless nowrap}}\{ {{/unless}}{{#each type.names}}{{#unless @first}} | {{/unless}}{{{this}}}{{/each}} {{#unless nowrap}}\}{{/unless}}{{/if}} 2 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc/event.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * {{{description}}} 3 | * {{#if type.names}}@type {{> type}}{{/if}} 4 | * @name {{{longname}}} 5 | * @kind event 6 | {{> params-table}} 7 | */ 8 | "{{{longname}}}": "", 9 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc-tech/typedef.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * {{{description}}} 3 | * @typedef {{> type}} {{#if parent}}{{parent.longname}}.{{/if}}{{{name}}} 4 | * 5 | {{> property-table}} 6 | */ 7 | 8 | {{> children children}} 9 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc/export-tree.hbs: -------------------------------------------------------------------------------- 1 | {{#each sub}} 2 | {{#unless link}} 3 | /** 4 | * @name {{full}} 5 | * @namespace 6 | */ 7 | "{{full}}": {}, 8 | {{/unless}} 9 | 10 | {{> export-tree}} 11 | {{/each}} 12 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc/params-table.hbs: -------------------------------------------------------------------------------- 1 | {{#each params}} 2 | * @param {{> type}} {{#if optional}}[{{/if}}{{{name}}}{{#if defaultValue}}={{{defaultValue}}}{{/if}}{{#if optional}}]{{/if}} - {{{description}}} 3 | {{/each}} 4 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc-tech/params-table.hbs: -------------------------------------------------------------------------------- 1 | {{#each params}} 2 | * @param {{> type}} {{#if optional}}[{{/if}}{{{name}}}{{#if defaultValue}}={{{defaultValue}}}{{/if}}{{#if optional}}]{{/if}} {{{description}}} 3 | {{/each}} 4 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-files/member.hbs: -------------------------------------------------------------------------------- 1 | #### {{#if parent}}{{> parents-line}}{{else inherits}}{{else}} *inner* {{/if}}{{{name}}} {{> type line=1}} 2 | 3 | {{{description}}} 4 | 5 | {{> property-table}} 6 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-single/member.hbs: -------------------------------------------------------------------------------- 1 | #### {{#if parent}}{{> parents-line}}{{else inherits}}{{else}} *inner* {{/if}}{{{name}}} {{> type line=1}} 2 | 3 | {{{description}}} 4 | 5 | {{> property-table}} 6 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc/property-table.hbs: -------------------------------------------------------------------------------- 1 | {{#each properties}} 2 | * @property {{> type}} {{#if optional}}[{{/if}}{{{name}}}{{#if defaultValue}}={{{defaultValue}}}{{/if}}{{#if optional}}]{{/if}} - {{{description}}} 3 | {{/each}} 4 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc-tech/property-table.hbs: -------------------------------------------------------------------------------- 1 | {{#each properties}} 2 | * @property {{> type}} {{#if optional}}[{{/if}}{{{name}}}{{#if defaultValue}}={{{defaultValue}}}{{/if}}{{#if optional}}]{{/if}} {{{description}}} 3 | {{/each}} 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | browserify 2 | uglify-js 3 | grunt 4 | grunt-browserify 5 | grunt-contrib-clean 6 | grunt-contrib-uglify 7 | grunt-mkdir 8 | grunt-contrib-copy 9 | node_modules/jsdoc 10 | node_modules/ym 11 | node_modules/.bin 12 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-files/namespace.hbs: -------------------------------------------------------------------------------- 1 | ## *ns* {{> parents-line}}{{{name}}} 2 | 3 | {{> exported}} 4 | 5 | 6 | {{{description}}} 7 | 8 | 9 | {{> property-table}} 10 | 11 | 12 | {{> children}} 13 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-single/namespace.hbs: -------------------------------------------------------------------------------- 1 | ## *ns* {{> parents-line}}{{{name}}} 2 | 3 | {{> exported}} 4 | 5 | 6 | {{{description}}} 7 | 8 | 9 | {{> property-table}} 10 | 11 | 12 | {{> children}} 13 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc/member.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * {{{description}}} 3 | * @name {{> fullname}} 4 | * {{#if type.names}}@type {{> type}}{{/if}} 5 | * {{#if const}}@const{{/if}} 6 | {{> property-table}} 7 | */ 8 | 9 | "{{> fullname}}": undefined, 10 | -------------------------------------------------------------------------------- /src/error/export.js: -------------------------------------------------------------------------------- 1 | require('../export'); 2 | 3 | var AudioError = require('./audio-error'); 4 | var PlaybackError = require('./playback-error'); 5 | 6 | ya.music.Audio.AudioError = AudioError; 7 | ya.music.Audio.PlaybackError = PlaybackError; 8 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc/namespace.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * {{{description}}} 3 | * @name {{{longname}}} 4 | * {{#if exported}}@alias {{{exported}}}{{/if}} 5 | {{> property-table}} 6 | * @namespace 7 | */ 8 | "{{{longname}}}": {}, 9 | 10 | {{> children children}} 11 | -------------------------------------------------------------------------------- /src/fx/equalizer/equalizer-static.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace EqualizerStatic 3 | * @private 4 | */ 5 | var EqualizerStatic = {}; 6 | 7 | /** @type {String} 8 | * @const*/ 9 | EqualizerStatic.EVENT_CHANGE = "change"; 10 | 11 | module.exports = EqualizerStatic; 12 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-files/index.hbs: -------------------------------------------------------------------------------- 1 | # Внешняя структура модуля 2 | 3 | {{#with exportTree}} 4 | {{#each symbols}} 5 | {{../indent}}* \{@link {{name}} {{path}}\} 6 | {{/each}} 7 | {{#each sub}} 8 | {{../indent}}* {{@key}} 9 | {{> export-tree}} 10 | {{/each}} 11 | {{/with}} 12 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-files/property-table.hbs: -------------------------------------------------------------------------------- 1 | {{#if properties}} 2 | | Имя | Тип | * | Описание | 3 | | --- | --- | --- | --- | 4 | {{#each properties}} 5 | | {{#if optional}}*\[{{/if}}{{{name}}}{{#if optional}}\]*{{/if}} | {{> type}} | {{{defaultvalue}}} | {{{description}}} | 6 | {{/each}} 7 | {{/if}} 8 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-single/property-table.hbs: -------------------------------------------------------------------------------- 1 | {{#if properties}} 2 | | Имя | Тип | * | Описание | 3 | | --- | --- | --- | --- | 4 | {{#each properties}} 5 | | {{#if optional}}*\[{{/if}}{{{name}}}{{#if optional}}\]*{{/if}} | {{> type}} | {{{defaultvalue}}} | {{{description}}} | 6 | {{/each}} 7 | {{/if}} 8 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc/typedef.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * {{{description}}} 3 | * @typedef {{> type}} {{{name}}} 4 | * {{#if parent}}@memberof {{parent.longname}}{{/if}} 5 | * {{#if static}}@static{{/if}} 6 | * {{#if inner}}@inner{{/if}} 7 | {{> property-table}} 8 | */ 9 | 10 | {{> children children}} 11 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-files/layout.hbs: -------------------------------------------------------------------------------- 1 | {{#if class}}{{> class class}}{{/if}} 2 | {{#if typedef}}{{> typedef typedef}}{{/if}} 3 | {{#if namespace}}{{> namespace namespace}}{{/if}} 4 | {{#if event}}{{> event event}}{{/if}} 5 | {{#if function}}{{> function function}}{{/if}} 6 | {{#if member}}{{> member member}}{{/if}} 7 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc/function.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * {{{description}}} 3 | * @function 4 | * @name {{> fullname}} 5 | * {{#if exported}}@alias {{exported}}{{/if}} 6 | {{> params-table}} 7 | * {{#if returns.type.names}}@returns {{>type returns}} - {{{returns.description}}}{{/if}} 8 | */ 9 | {{> method}} 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | require('./lib/browser/audioContextMonkeyPatch.js'); 2 | 3 | var YandexAudio = require('./export'); 4 | require('./error/export'); 5 | require('./lib/net/error/export'); 6 | require('./logger/export'); 7 | require('./fx/equalizer/export'); 8 | require('./fx/volume/export'); 9 | 10 | module.exports = YandexAudio; 11 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-single/children.hbs: -------------------------------------------------------------------------------- 1 | {{#each children.member}} 2 | 3 | {{> member}} 4 | 5 | {{/each}} 6 | 7 | 8 | {{#each children.function}} 9 | 10 | {{> function}} 11 | 12 | {{/each}} 13 | 14 | 15 | {{#each children.event}} 16 | 17 | {{> event}} 18 | 19 | {{/each}} 20 | 21 | 22 | {{#each children.namespace}} 23 | 24 | {{> namespace}} 25 | 26 | {{/each}} 27 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc-tech/member.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * {{{description}}} 3 | * @field 4 | * @name {{{name}}} 5 | * {{#if type.names}}@type {{> type nowrap=1}}{{/if}} 6 | * {{#if const}}@const{{/if}} 7 | * {{#if parent}}@memberof {{{parent.longname}}}{{/if}} 8 | * {{#if inner}}@inner {{/if}} 9 | * {{#if static}}@static {{/if}} 10 | {{> property-table}} 11 | * 12 | */ 13 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-files/params-table.hbs: -------------------------------------------------------------------------------- 1 | {{#if params}} 2 | {{#if line}} 3 | {{#each params}}{{#unless @first}}, {{/unless}}{{{name}}}{{> type line=1}}{{/each}} 4 | {{else}} 5 | | Имя | Тип | * | Описание | 6 | | --- | --- | --- | --- | 7 | {{#each params}} 8 | | {{#if optional}}*\[{{/if}}{{{name}}}{{#if optional}}\]*{{/if}} | {{> type}} | {{{defaultvalue}}} | {{{description}}} | 9 | {{/each}} 10 | {{/if}} 11 | {{/if}} 12 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-single/params-table.hbs: -------------------------------------------------------------------------------- 1 | {{#if params}} 2 | {{#if line}} 3 | {{#each params}}{{#unless @first}}, {{/unless}}{{{name}}}{{> type line=1}}{{/each}} 4 | {{else}} 5 | | Имя | Тип | * | Описание | 6 | | --- | --- | --- | --- | 7 | {{#each params}} 8 | | {{#if optional}}*\[{{/if}}{{{name}}}{{#if optional}}\]*{{/if}} | {{> type}} | {{{defaultvalue}}} | {{{description}}} | 9 | {{/each}} 10 | {{/if}} 11 | {{/if}} 12 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc/class.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * @name {{name}} 3 | * @kind class 4 | * @classdesc {{{classdesc}}} 5 | * 6 | {{#each augments}} 7 | * @extends {{this}} 8 | {{/each}} 9 | * 10 | {{#each fires}} 11 | * @fires {{this}} 12 | {{/each}} 13 | *{{#if exported}} @alias {{{exported}}} 14 | * 15 | * @constructor 16 | * {{{description}}} 17 | {{> params-table}} 18 | * 19 | {{/if}} 20 | */ 21 | {{> method}} 22 | 23 | {{> children children}} 24 | -------------------------------------------------------------------------------- /src/lib/class/pure-instance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Создаёт экземпляр класса, но не запускает его конструктор 3 | * @param {function} OriginalClass - класс 4 | * @exported ya.music.lib.pureInstance 5 | * @returns {OriginalClass} 6 | */ 7 | var pureInstance = function(OriginalClass) { 8 | var PureClass = function() {}; 9 | PureClass.prototype = OriginalClass.prototype; 10 | return new PureClass(); 11 | }; 12 | 13 | module.exports = pureInstance; 14 | -------------------------------------------------------------------------------- /src/lib/async/reject.js: -------------------------------------------------------------------------------- 1 | var noop = require('../noop'); 2 | var Promise = require('./promise'); 3 | 4 | /** 5 | * Содание отклонённого обещания, которое не плюётся в консоль ошибкой 6 | * @param {Error} data - причина отклонения обещания 7 | * @returns {Promise} 8 | * @private 9 | */ 10 | var reject = function(data) { 11 | var promise = Promise.reject(data); 12 | promise["catch"](noop); 13 | return promise; 14 | }; 15 | 16 | module.exports = reject; 17 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc-tech/function.hbs: -------------------------------------------------------------------------------- 1 | {{#unless inherited}} 2 | /** 3 | * {{{description}}} 4 | * @function 5 | * @name {{name}} 6 | * {{#if exported}}@alias {{exported}}{{/if}} 7 | * {{#if parent}}@memberof {{parent.longname}}{{/if}} 8 | * {{#if inner}}@inner{{/if}} 9 | * {{#unless parent.namespace}}{{#if static}}@static{{/if}}{{/unless}} 10 | {{> params-table}} 11 | * {{#if returns.type.names}}@returns {{>type returns}} {{{returns.description}}}{{/if}} 12 | */ 13 | {{/unless}} 14 | -------------------------------------------------------------------------------- /spec/LoaderError.md: -------------------------------------------------------------------------------- 1 | ## *class* LoaderError 2 | 3 | **Доступен извне как:** `ya.music.Audio.LoaderError` 4 | 5 | Класс ошибок загрузчика. 6 | Расширяет Error. 7 | 8 | #### new LoaderError(message: String) 9 | 10 | | Имя | Тип | * | Описание | 11 | | --- | --- | --- | --- | 12 | | message | String | | Текст ошибки. | 13 | 14 | #### LoaderError.TIMEOUT : String 15 | 16 | Таймаут загрузки. 17 | 18 | #### LoaderError.FAILED : String 19 | 20 | Ошибка запроса на загрузку. 21 | 22 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc-tech/class.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * @class {{{classdesc}}} 3 | * @name {{{name}}} 4 | * {{#if parent}}@memberof {{parent.longname}}{{/if}} 5 | * {{#if static}}@static{{/if}} 6 | * {{#if inner}}@inner{{/if}} 7 | * 8 | {{#each augments}} 9 | * @extends {{this}} 10 | {{/each}} 11 | * 12 | {{#each fires}} 13 | * @fires {{this}} 14 | {{/each}} 15 | * 16 | {{#if exported}} 17 | * @alias {{{exported}}} 18 | * 19 | * @constructor 20 | * {{{description}}} 21 | {{> params-table}} 22 | * {{else}} 23 | * @noconstructor 24 | {{/if}} 25 | */ 26 | 27 | {{> children children}} 28 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-files/class.hbs: -------------------------------------------------------------------------------- 1 | ##{{#unless exported}} *inner*{{/unless}} *class* {{{name}}} 2 | 3 | {{> exported}} 4 | 5 | 6 | {{{classdesc}}} 7 | 8 | {{#if augments}} 9 | **Расширяет:** 10 | 11 | {{#each augments}} 12 | - \{@link {{this}}\} 13 | {{/each}} 14 | {{/if}} 15 | 16 | {{#if fires}} 17 | **Триггерит:** 18 | 19 | {{#each fires}} 20 | - \{@link {{this}}\} 21 | {{/each}} 22 | {{/if}} 23 | 24 | {{#if exported}} 25 | #### new {{{name}}}({{> params-table line=1}}) 26 | 27 | {{{description}}} 28 | 29 | {{> params-table}} 30 | {{/if}} 31 | 32 | {{> children}} 33 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-files/function.hbs: -------------------------------------------------------------------------------- 1 | #### {{#if parent}} 2 | {{> parents-line}} 3 | {{else exported}} 4 | 5 | {{else}} 6 | *inner* 7 | {{/if}}{{{name}}} ({{> params-table line=1}}) {{>type returns line=1}} {{#if inherited}}*(inherits {@link {{inherits}}\})*{{/if}} 8 | 9 | {{> exported}} 10 | 11 | 12 | {{{description}}} 13 | 14 | 15 | {{> params-table}} 16 | 17 | 18 | {{#if returns.description}} 19 | > **Возвращает:** {{{returns.description}}} 20 | {{/if}} 21 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-single/function.hbs: -------------------------------------------------------------------------------- 1 | #### {{#if parent}} 2 | {{> parents-line}} 3 | {{else exported}} 4 | 5 | {{else}} 6 | *inner* 7 | {{/if}}{{{name}}} ({{> params-table line=1}}) {{>type returns line=1}} {{#if inherited}}*(inherits {@link {{inherits}}\})*{{/if}} 8 | 9 | {{> exported}} 10 | 11 | 12 | {{{description}}} 13 | 14 | 15 | {{> params-table}} 16 | 17 | 18 | {{#if returns.description}} 19 | > **Возвращает:** {{{returns.description}}} 20 | {{/if}} 21 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc/children.hbs: -------------------------------------------------------------------------------- 1 | {{#if member}} 2 | /*************************************************************/ 3 | 4 | // Members 5 | {{#each member}} 6 | 7 | {{> member}} 8 | 9 | {{/each}} 10 | {{/if}} 11 | 12 | 13 | {{#if function}} 14 | /*************************************************************/ 15 | 16 | // Functions 17 | {{#each function}} 18 | 19 | {{> function}} 20 | 21 | {{/each}} 22 | {{/if}} 23 | 24 | 25 | {{#if event}} 26 | /*************************************************************/ 27 | 28 | // Events 29 | {{#each event}} 30 | 31 | {{> event}} 32 | 33 | {{/each}} 34 | {{/if}} 35 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-single/layout.hbs: -------------------------------------------------------------------------------- 1 | ## Внешняя структура модуля 2 | 3 | {{> export-tree exportTree}} 4 | 5 | {{#each linear.class}} 6 | ---- 7 | 8 | {{> class}} 9 | 10 | {{/each}} 11 | 12 | 13 | {{#if linear.function}} 14 | ---- 15 | 16 | ## Функции 17 | {{#each linear.function}} 18 | 19 | {{> function}} 20 | {{/each}} 21 | {{/if}} 22 | 23 | 24 | {{#if linear.typedef}} 25 | ---- 26 | 27 | ## Типы 28 | {{#each linear.typedef}} 29 | 30 | {{> typedef}} 31 | {{/each}} 32 | {{/if}} 33 | 34 | 35 | ## Пространства имён 36 | 37 | {{#each tree.namespace}} 38 | ---- 39 | 40 | {{> namespace}} 41 | 42 | {{/each}} 43 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc-tech/children.hbs: -------------------------------------------------------------------------------- 1 | {{#if member}} 2 | /*************************************************************/ 3 | 4 | // Members 5 | {{#each member}} 6 | 7 | {{> member}} 8 | 9 | {{/each}} 10 | {{/if}} 11 | 12 | 13 | {{#if function}} 14 | /*************************************************************/ 15 | 16 | // Functions 17 | {{#each function}} 18 | 19 | {{> function}} 20 | 21 | {{/each}} 22 | {{/if}} 23 | 24 | 25 | {{#if event}} 26 | /*************************************************************/ 27 | 28 | // Events 29 | {{#each event}} 30 | 31 | {{> event}} 32 | 33 | {{/each}} 34 | {{/if}} 35 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-single/class.hbs: -------------------------------------------------------------------------------- 1 | ##{{#unless exported}} *inner*{{/unless}} *class* {{{name}}} 2 | 3 | {{> exported}} 4 | 5 | 6 | {{{classdesc}}} 7 | 8 | 9 | {{#if augments}} 10 | **Расширяет:** 11 | 12 | {{#each augments}} 13 | - \{@link {{this}}\} 14 | {{/each}} 15 | {{/if}} 16 | 17 | 18 | {{#if fires}} 19 | **Триггерит:** 20 | 21 | {{#each fires}} 22 | - \{@link {{this}}\} 23 | {{/each}} 24 | {{/if}} 25 | 26 | 27 | {{#if exported}} 28 | #### new {{{name}}}({{> params-table line=1}}) 29 | 30 | 31 | {{{description}}} 32 | 33 | 34 | {{> params-table}} 35 | {{/if}} 36 | 37 | 38 | {{> children}} 39 | -------------------------------------------------------------------------------- /src/export.js: -------------------------------------------------------------------------------- 1 | if (typeof DEV === "undefined") { 2 | window.DEV = true; 3 | } 4 | 5 | if (typeof window.ya === "undefined") { 6 | window.ya = {}; 7 | } 8 | 9 | var ya = window.ya; 10 | 11 | if (typeof ya.music === "undefined") { 12 | ya.music = {}; 13 | } 14 | 15 | if (typeof ya.music.Audio === "undefined") { 16 | ya.music.Audio = {}; 17 | } 18 | 19 | var config = require('./config'); 20 | var AudioPlayer = require('./audio-player'); 21 | var Proxy = require('./lib/class/proxy'); 22 | 23 | ya.music.Audio = Proxy.createClass(AudioPlayer); 24 | ya.music.Audio.config = config; 25 | 26 | require('./lib/export'); 27 | 28 | module.exports = ya.music.Audio; 29 | -------------------------------------------------------------------------------- /spec/readme.md: -------------------------------------------------------------------------------- 1 | # Внешняя структура модуля 2 | 3 | * ya.music 4 | * [Audio](Audio.md#Audio) 5 | * [config](config.md#config) 6 | * [AudioError](AudioError.md#AudioError) 7 | * [PlaybackError](PlaybackError.md#PlaybackError) 8 | * fx 9 | * [Equalizer](Equalizer.md#Equalizer) 10 | * [volumeLib](volumeLib.md#volumeLib) 11 | * [LoaderError](LoaderError.md#LoaderError) 12 | * lib 13 | * [Deferred](Deferred.md#Deferred) 14 | * [Events](Events.md#Events) 15 | * [Promise](Promise.md#Promise) 16 | * [Error](ErrorClass.md#ErrorClass) 17 | * [pureInstance](global.md#pureInstance) 18 | * [merge](global.md#merge) 19 | * [info](info.md#info) 20 | * [Logger](Logger.md#Logger) 21 | -------------------------------------------------------------------------------- /src/lib/net/error/loader-error.js: -------------------------------------------------------------------------------- 1 | var ErrorClass = require('../../class/error-class'); 2 | 3 | /** 4 | * @exported ya.music.Audio.LoaderError 5 | * @classdesc Класс ошибок загрузчика. 6 | * Расширяет Error. 7 | * @param {String} message Текст ошибки. 8 | * 9 | * @constructor 10 | */ 11 | var LoaderError = function(message) { 12 | ErrorClass.call(this, message); 13 | }; 14 | LoaderError.prototype = ErrorClass.create("LoaderError"); 15 | 16 | /** 17 | * Таймаут загрузки. 18 | * @type {String} 19 | * @const 20 | */ 21 | LoaderError.TIMEOUT = "request timeout"; 22 | /** 23 | * Ошибка запроса на загрузку. 24 | * @type {String} 25 | * @const 26 | */ 27 | LoaderError.FAILED = "request failed"; 28 | 29 | module.exports = LoaderError; 30 | -------------------------------------------------------------------------------- /jsdoc/doc/gfm-files/children.hbs: -------------------------------------------------------------------------------- 1 | {{#each children.member}} 2 | 3 | {{> member}} 4 | 5 | {{/each}} 6 | 7 | 8 | {{#if children.event}} 9 | ---- 10 | 11 | ### События 12 | {{#each children.event}} 13 | 14 | {{> event}} 15 | 16 | {{/each}} 17 | {{/if}} 18 | 19 | {{#if children.function}} 20 | ---- 21 | 22 | ### Методы 23 | {{#each children.function}} 24 | 25 | {{> function}} 26 | 27 | {{/each}} 28 | {{/if}} 29 | 30 | 31 | {{#if children.namespace}} 32 | ---- 33 | 34 | ### Пространства имён 35 | {{#each children.namespace}} 36 | 37 | 38 | {{> namespace}} 39 | 40 | {{/each}} 41 | {{/if}} 42 | 43 | 44 | {{#if children.typedef}} 45 | ---- 46 | 47 | ### Типы 48 | {{#each children.typedef}} 49 | 50 | {{> typedef}} 51 | 52 | {{/each}} 53 | {{/if}} 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "YandexAudio", 3 | "version": "0.0.1", 4 | "description": "Audio-player library for browser", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/yandex/music-audio.git" 12 | }, 13 | "keywords": [ 14 | "audio", 15 | "html5", 16 | "flash" 17 | ], 18 | "author": "Silaev Mikhail ", 19 | "license": "GNU LGPL v3.0", 20 | "devDependencies": { 21 | "browserify": "^11.0.1", 22 | "handlebars": "^4.0.5", 23 | "jsdoc": "^3.3.2", 24 | "uglify-js": "^2.4.24" 25 | }, 26 | "dependencies": { 27 | "vow": "^0.4.10" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spec/Deferred.md: -------------------------------------------------------------------------------- 1 | ## *class* Deferred 2 | 3 | **Доступен извне как:** `ya.music.lib.Deferred` 4 | 5 | Класс для управления обещанием 6 | 7 | #### new Deferred() 8 | 9 | ---- 10 | 11 | ### Методы 12 | 13 | #### Deferred#resolve (data) 14 | 15 | Разрешить обещание 16 | 17 | | Имя | Тип | * | Описание | 18 | | --- | --- | --- | --- | 19 | | data | | | передать данные в обещание | 20 | 21 | #### Deferred#reject (error) 22 | 23 | Отклонить обещание 24 | 25 | | Имя | Тип | * | Описание | 26 | | --- | --- | --- | --- | 27 | | error | | | передать ошибку | 28 | 29 | #### Deferred#promise () : [Promise](Promise.md#Promise) 30 | 31 | Получить обещание 32 | 33 | -------------------------------------------------------------------------------- /examples/quick-start/index.html: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /spec/ErrorClass.md: -------------------------------------------------------------------------------- 1 | ## *class* ErrorClass 2 | 3 | **Доступен извне как:** `ya.music.lib.Error` 4 | 5 | Класс ошибки. Оригинальный Error ведёт себя как фабрика, а не как класс. Этот объект ведёт себя как класс и его можно наследовать. 6 | 7 | **Расширяет:** 8 | 9 | - Error 10 | 11 | #### new ErrorClass(message: String, id: Number) 12 | 13 | | Имя | Тип | * | Описание | 14 | | --- | --- | --- | --- | 15 | | *\[message\]* | String | | сообщение | 16 | | *\[id\]* | Number | | идентификатор ошибки | 17 | 18 | ---- 19 | 20 | ### Методы 21 | 22 | #### ErrorClass.create (name: String) : [ErrorClass](ErrorClass.md#ErrorClass) 23 | 24 | Сахар для быстрого создания нового класса ошибок. 25 | 26 | | Имя | Тип | * | Описание | 27 | | --- | --- | --- | --- | 28 | | name | String | | имя создаваемого класса | 29 | 30 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc-tech/layout.hbs: -------------------------------------------------------------------------------- 1 | {{#if linear.class}} 2 | /*************************************************************/ 3 | 4 | // Classes 5 | 6 | {{#each linear.class}} 7 | //-----------------------------------------------------------// 8 | 9 | {{> class}} 10 | {{/each}} 11 | {{/if}} 12 | 13 | 14 | {{#if linear.typedef}} 15 | /*************************************************************/ 16 | 17 | // Typedefs 18 | {{#each linear.typedef}} 19 | //-----------------------------------------------------------// 20 | 21 | {{> typedef}} 22 | {{/each}} 23 | {{/if}} 24 | 25 | 26 | {{#if linear.namespace}} 27 | /*************************************************************/ 28 | 29 | // Namespaces 30 | {{#each linear.namespace}} 31 | //-----------------------------------------------------------// 32 | 33 | {{> namespace}} 34 | {{/each}} 35 | {{/if}} 36 | 37 | {{> children linear}} 38 | -------------------------------------------------------------------------------- /spec/global.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | #### pureInstance (OriginalClass: function) : OriginalClass 4 | 5 | **Доступен извне как:** `ya.music.lib.pureInstance` 6 | 7 | Создаёт экземпляр класса, но не запускает его конструктор 8 | 9 | | Имя | Тип | * | Описание | 10 | | --- | --- | --- | --- | 11 | | OriginalClass | function | | класс | 12 | 13 | 14 | 15 | 16 | 17 | #### merge (initial: Object, ...args: Object, extend: Boolean) : Object 18 | 19 | **Доступен извне как:** `ya.music.lib.merge` 20 | 21 | Объединение объектов 22 | 23 | | Имя | Тип | * | Описание | 24 | | --- | --- | --- | --- | 25 | | initial | Object | | начальный объект | 26 | | ...args | Object | | список объектов которые надо объединить с начальным | 27 | | *\[extend\]* | Boolean | | если последний аргумент true, то будет модифицирован начальный объект, в противном случае будет создана неглубокая копия. | 28 | 29 | -------------------------------------------------------------------------------- /spec/info.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## *ns* info 4 | 5 | **Доступен извне как:** `ya.music.info` 6 | 7 | Информация об окружении 8 | 9 | #### info.onlyDeviceVolume : boolean 10 | 11 | Настройка громкости 12 | 13 | ---- 14 | 15 | ### Пространства имён 16 | 17 | ## *ns* info.browser 18 | 19 | Информация о браузере 20 | 21 | | Имя | Тип | * | Описание | 22 | | --- | --- | --- | --- | 23 | | name | string | | название браузера | 24 | | version | string | | версия | 25 | | *\[documentMode\]* | number | | версия документа (для IE) | 26 | 27 | ## *ns* info.platform 28 | 29 | Информация о платформе 30 | 31 | | Имя | Тип | * | Описание | 32 | | --- | --- | --- | --- | 33 | | os | string | | тип операционной системы | 34 | | type | string | | тип платформы | 35 | | tablet | boolean | | планшет | 36 | | mobile | boolean | | мобильный | 37 | 38 | -------------------------------------------------------------------------------- /test/web-audio-api/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 27 | 28 | 29 | 30 |
31 |
{{title}}
32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc/layout.hbs: -------------------------------------------------------------------------------- 1 | ({ 2 | {{> export-tree exportTree}} 3 | 4 | {{#if linear.class}} 5 | /*************************************************************/ 6 | 7 | // Classes 8 | 9 | {{#each linear.class}} 10 | //-----------------------------------------------------------// 11 | 12 | {{> class}} 13 | {{/each}} 14 | {{/if}} 15 | 16 | 17 | {{#if linear.typedef}} 18 | /*************************************************************/ 19 | 20 | // Typedefs 21 | {{#each linear.typedef}} 22 | //-----------------------------------------------------------// 23 | 24 | {{> typedef}} 25 | {{/each}} 26 | {{/if}} 27 | 28 | 29 | {{#if linear.namespace}} 30 | /*************************************************************/ 31 | 32 | // Namespaces 33 | {{#each linear.namespace}} 34 | //-----------------------------------------------------------// 35 | 36 | {{> namespace}} 37 | {{/each}} 38 | {{/if}} 39 | 40 | {{> children linear}} 41 | }) 42 | -------------------------------------------------------------------------------- /spec/PlaybackError.md: -------------------------------------------------------------------------------- 1 | ## *class* PlaybackError 2 | 3 | **Доступен извне как:** `ya.music.Audio.PlaybackError` 4 | 5 | Класс ошибки воспроизведения. 6 | 7 | **Расширяет:** 8 | 9 | - Error 10 | 11 | #### new PlaybackError(message: String, src: String) 12 | 13 | | Имя | Тип | * | Описание | 14 | | --- | --- | --- | --- | 15 | | message | String | | Текст ошибки. | 16 | | src | String | | Ссылка на трек. | 17 | 18 | #### PlaybackError.CONNECTION_ABORTED : String 19 | 20 | Отмена соединенния. 21 | 22 | #### PlaybackError.NETWORK_ERROR : String 23 | 24 | Сетевая ошибка. 25 | 26 | #### PlaybackError.DECODE_ERROR : String 27 | 28 | Ошибка декодирования аудио. 29 | 30 | #### PlaybackError.BAD_DATA : String 31 | 32 | Недоступный источник. 33 | 34 | #### PlaybackError.DONT_START : String 35 | 36 | Не запускается воспроизведение. 37 | 38 | #### PlaybackError.html5 : Object 39 | 40 | Таблица соответствия кодов ошибок HTML5 плеера. 41 | 42 | -------------------------------------------------------------------------------- /src/lib/export.js: -------------------------------------------------------------------------------- 1 | require('../export'); 2 | 3 | if (!ya.music.lib) { 4 | ya.music.lib = {}; 5 | } 6 | 7 | var Events = require('./async/events'); 8 | var ErrorClass = require('./class/error-class'); 9 | var pureInstance = require('./class/pure-instance'); 10 | 11 | var EventsProxy = function() { Events.call(this); }; 12 | EventsProxy.prototype = pureInstance(Events); 13 | EventsProxy.mixin = Events.mixin; 14 | EventsProxy.eventize = Events.eventize; 15 | 16 | var ErrorClassProxy = function() { ErrorClass.apply(this, arguments); }; 17 | ErrorClassProxy.prototype = pureInstance(ErrorClass); 18 | ErrorClassProxy.create = ErrorClass.create; 19 | 20 | ya.music.lib.Events = EventsProxy; 21 | ya.music.lib.Error = ErrorClassProxy; 22 | 23 | ya.music.lib.Promise = require('./async/promise'); 24 | ya.music.lib.Deferred = require('./async/deferred'); 25 | 26 | ya.music.lib.pureInstance = pureInstance; 27 | ya.music.lib.merge = require('./data/merge'); 28 | 29 | ya.music.info = require('./browser/detect'); 30 | -------------------------------------------------------------------------------- /src/flash/src/AudioEvent.as: -------------------------------------------------------------------------------- 1 | package { 2 | import flash.events.DataEvent; 3 | 4 | public final class AudioEvent extends DataEvent { 5 | public var offset:int; 6 | 7 | public static const EVENT_PLAY:String = "play"; 8 | public static const EVENT_PAUSE:String = "pause"; 9 | public static const EVENT_PROGRESS:String = "progress"; 10 | public static const EVENT_ENDED:String = "ended"; 11 | public static const EVENT_ERROR:String = "error"; 12 | public static const EVENT_VOLUME:String = "volumechange"; 13 | public static const EVENT_STOP:String = "stop"; 14 | public static const EVENT_LOADED:String = "loaded"; 15 | public static const EVENT_LOADING:String = "loading"; 16 | public static const EVENT_SWAP:String = "swap"; 17 | 18 | public static const EVENT_DEBUG:String = "debug"; 19 | 20 | public function AudioEvent(type:String, offset:int = -1, data:String = "") { 21 | super(type, false, false, data); 22 | this.offset = offset; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/async/deferred.js: -------------------------------------------------------------------------------- 1 | var Promise = require('./promise'); 2 | var noop = require('../noop'); 3 | 4 | /** 5 | * @classdesc Класс для управления обещанием 6 | * @exported ya.music.lib.Deferred 7 | * @constructor 8 | */ 9 | var Deferred = function() { 10 | var self = this; 11 | 12 | var promise = new Promise(function(resolve, reject) { 13 | /** 14 | * Разрешить обещание 15 | * @method Deferred#resolve 16 | * @param data - передать данные в обещание 17 | */ 18 | self.resolve = resolve; 19 | 20 | /** 21 | * Отклонить обещание 22 | * @method Deferred#reject 23 | * @param error - передать ошибку 24 | */ 25 | self.reject = reject; 26 | }); 27 | 28 | promise["catch"](noop); // Don't throw errors to console 29 | 30 | /** 31 | * Получить обещание 32 | * @method Deferred#promise 33 | * @returns {Promise} 34 | */ 35 | this.promise = function() { return promise; }; 36 | }; 37 | 38 | module.exports = Deferred; 39 | -------------------------------------------------------------------------------- /src/lib/class/error-class.js: -------------------------------------------------------------------------------- 1 | var pureInstance = require('./pure-instance'); 2 | 3 | /** 4 | * @classdesc Класс ошибки. Оригинальный Error ведёт себя как фабрика, а не как класс. Этот объект ведёт себя как класс и его можно наследовать. 5 | * @param {String} [message] - сообщение 6 | * @param {Number} [id] - идентификатор ошибки 7 | * @extends Error 8 | * @exported ya.music.lib.Error 9 | * @constructor 10 | */ 11 | var ErrorClass = function(message, id) { 12 | var err = new Error(message, id); 13 | err.name = this.name; 14 | 15 | this.message = err.message; 16 | this.stack = err.stack; 17 | }; 18 | 19 | /** 20 | * Сахар для быстрого создания нового класса ошибок. 21 | * @param {String} name - имя создаваемого класса 22 | * @returns {ErrorClass} 23 | */ 24 | ErrorClass.create = function(name) { 25 | var errClass = pureInstance(this); 26 | errClass.name = name; 27 | return errClass; 28 | }; 29 | 30 | ErrorClass.prototype = pureInstance(Error); 31 | ErrorClass.prototype.name = "ErrorClass"; 32 | 33 | module.exports = ErrorClass; 34 | -------------------------------------------------------------------------------- /jsdoc/doc/jsdoc-tech/namespace.hbs: -------------------------------------------------------------------------------- 1 | {{#if parent.namespace}} 2 | /** 3 | * {{{description}}} 4 | * 5 | * ПараметрЗначение по умолчаниюОписание 6 | {{#each children.member}} 7 | * {{{name}}}{{>type nowrap=1}}{{{description}}} 8 | {{/each}}{{#each properties}} 9 | * {{{name}}}{{>type nowrap=1}}{{{description}}} 10 | {{/each}} 11 | *
12 | * @name {{{longname}}} 13 | * @type Object 14 | */ 15 | 16 | 17 | {{else}} 18 | /** 19 | * @namespace {{{description}}} 20 | * @name {{{longname}}} 21 | * {{#if exported}}@alias {{{exported}}}{{/if}} 22 | * {{#if inner}}@inner{{/if}} 23 | * {{#if static}}@static{{/if}} 24 | */ 25 | 26 | {{> children children}} 27 | {{/if}} 28 | 29 | -------------------------------------------------------------------------------- /src/lib/data/merge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Объединение объектов 3 | * @param {Object} initial - начальный объект 4 | * @param {Object} ...args - список объектов которые надо объединить с начальным 5 | * @param {Boolean} [extend] - если последний аргумент true, то будет модифицирован начальный объект, в противном случае будет создана неглубокая копия. 6 | * @exported ya.music.lib.merge 7 | * @returns {Object} 8 | */ 9 | var merge = function(initial) { 10 | var args = [].slice.call(arguments, 1); 11 | var object; 12 | var key; 13 | 14 | if (args[args.length - 1] === true) { 15 | object = initial; 16 | args.pop(); 17 | } else { 18 | object = {}; 19 | for (key in initial) { 20 | if (initial.hasOwnProperty(key)) { 21 | object[key] = initial[key]; 22 | } 23 | } 24 | } 25 | 26 | for (var k = 0, l = args.length; k < l; k++) { 27 | for (key in args[k]) { 28 | if (args[k].hasOwnProperty(key)) { 29 | object[key] = args[k][key]; 30 | } 31 | } 32 | } 33 | 34 | return object; 35 | }; 36 | 37 | module.exports = merge; 38 | -------------------------------------------------------------------------------- /jsdoc/jsdoc.public.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true 4 | }, 5 | "source": { 6 | "include": [ 7 | "src/" 8 | ], 9 | "exclude": [], 10 | "includePattern": ".+\\.(js)(doc)?$" 11 | }, 12 | "plugins": ["plugins/markdown"], 13 | "markdown": { 14 | "parser": "gfm", 15 | "hardwrap": false 16 | }, 17 | 18 | "templates": { 19 | "applicationName": "YandexAudio", 20 | "disqus": "", 21 | "googleAnalytics": "", 22 | "openGraph": { 23 | "title": "", 24 | "type": "website", 25 | "image": "", 26 | "site_name": "", 27 | "url": "" 28 | }, 29 | "meta": { 30 | "title": "", 31 | "description": "", 32 | "keyword": "" 33 | }, 34 | "linenums": true 35 | }, 36 | 37 | "opts": { 38 | "template": "jsdoc/doc", 39 | "recurse": true, 40 | "encoding": "utf8", 41 | "destination": "spec/", 42 | "private": false, 43 | "lenient": true, 44 | "readme": "readme.md", 45 | "tutorials": "tutorial/" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /spec/Promise.md: -------------------------------------------------------------------------------- 1 | ## *class* Promise 2 | 3 | **Доступен извне как:** `ya.music.lib.Promise` 4 | 5 | Обещание по спецификации [ES 2015 promises](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Promise). В устаревших браузерах и IE используется замена из библиотеки [vow](http://github.com/dfilatov/vow.git) 6 | 7 | #### new Promise() 8 | 9 | ---- 10 | 11 | ### Методы 12 | 13 | #### Promise#then (callback: function, errback: null \| function) : [Promise](Promise.md#Promise) 14 | 15 | Назначить обработчики разрешения и отклонения обещания. 16 | 17 | | Имя | Тип | * | Описание | 18 | | --- | --- | --- | --- | 19 | | callback | function | | Обработчик успеха. | 20 | | *\[errback\]* | null \| function | | Обработчик ошибки. | 21 | 22 | > **Возвращает:** новое обещание из результатов обработчика. 23 | 24 | #### Promise#catch (errback: function) : [Promise](Promise.md#Promise) 25 | 26 | Назначить обработчик отклонения обещания. 27 | 28 | | Имя | Тип | * | Описание | 29 | | --- | --- | --- | --- | 30 | | errback | function | | Обработчик ошибки. | 31 | 32 | > **Возвращает:** новое обещание из результатов обработчика. 33 | 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NPM_BIN=./node_modules/.bin 2 | 3 | SOURCEDIR=src 4 | BUILDDIR=dist 5 | 6 | JSDOC=$(NPM_BIN)/jsdoc -c 7 | UGLIFY_JS=$(NPM_BIN)/uglifyjs --mangle --compress --bare-returns --stats 8 | BROWSERIFY=$(NPM_BIN)/browserify -d 9 | 10 | 11 | all: clean build minify jsdoc 12 | git add -A 13 | git commit 14 | 15 | 16 | clean: 17 | -rm -rf $(BUILDDIR) 18 | 19 | 20 | npm: 21 | -npm install 22 | 23 | 24 | prepare: clean npm 25 | mkdir $(BUILDDIR) 26 | cp $(SOURCEDIR)/flash/build/*.swf $(BUILDDIR)/ 27 | 28 | 29 | build: $(BUILDDIR)/index.js 30 | 31 | 32 | minify: $(BUILDDIR)/index.min.js 33 | 34 | 35 | $(BUILDDIR)/index.js: prepare 36 | $(BROWSERIFY) $(SOURCEDIR)/index.js > $(BUILDDIR)/index.js 37 | 38 | 39 | $(BUILDDIR)/index.min.js: $(BUILDDIR)/index.js 40 | $(UGLIFY_JS) $(BUILDDIR)/index.js > $(BUILDDIR)/index.min.js --source-map $(BUILDDIR)/index.map.json 41 | 42 | 43 | jsdoc: npm 44 | -rm -rf spec/* 45 | -mkdir spec 46 | $(JSDOC) jsdoc/jsdoc.public.json -q "style=gfm-files" 47 | $(JSDOC) jsdoc/jsdoc.public.json -q "style=gfm-single&out=full" 48 | $(JSDOC) jsdoc/jsdoc.public.json -d $(BUILDDIR)/ -q "style=jsdoc&out=audio" 49 | $(JSDOC) jsdoc/jsdoc.public.json -q "style=jsdoc-tech&out=tech" 50 | 51 | 52 | .PHONY: all clean build minify prepare npm jsdoc 53 | -------------------------------------------------------------------------------- /spec/AudioError.md: -------------------------------------------------------------------------------- 1 | ## *class* AudioError 2 | 3 | **Доступен извне как:** `ya.music.Audio.AudioError` 4 | 5 | Класс ошибки аудиопллеера. 6 | 7 | **Расширяет:** 8 | 9 | - Error 10 | 11 | #### new AudioError(message: String) 12 | 13 | | Имя | Тип | * | Описание | 14 | | --- | --- | --- | --- | 15 | | message | String | | Текст ошибки. | 16 | 17 | #### AudioError.NO_IMPLEMENTATION : String 18 | 19 | Не найдена реализация плеера или возникла ошибка при инициализации всех доступных реализаций. 20 | 21 | #### AudioError.NOT_PRELOADED : String 22 | 23 | Аудиофайл не был предзагружен или во время загрузки произошла ошибка. 24 | 25 | #### AudioError.BAD_STATE : String 26 | 27 | Действие недоступно из текущего состояния. 28 | 29 | #### AudioError.FLASH_BLOCKER : String 30 | 31 | Flash-плеер был заблокирован. 32 | 33 | #### AudioError.FLASH_UNKNOWN_CRASH : String 34 | 35 | Возникла ошибка при инициализации Flash-плеера по неизвестным причинам. 36 | 37 | #### AudioError.FLASH_INIT_TIMEOUT : String 38 | 39 | Возникла ошибка при инициализации Flash-плеера из-за таймаута. 40 | 41 | #### AudioError.FLASH_INTERNAL_ERROR : String 42 | 43 | Внутренняя ошибка Flash-плеера. 44 | 45 | #### AudioError.FLASH_EMMITER_NOT_FOUND : String 46 | 47 | Попытка вызвать недоступный экземляр Flash-плеера. 48 | 49 | #### AudioError.FLASH_NOT_RESPONDING : String 50 | 51 | Flash-плеер перестал отвечать на запросы. 52 | 53 | -------------------------------------------------------------------------------- /spec/Logger.md: -------------------------------------------------------------------------------- 1 | ## *class* Logger 2 | 3 | **Доступен извне как:** `ya.music.Logger` 4 | 5 | Настраиваемый логгер для аудиоплеера. 6 | 7 | #### new Logger(channel: String) 8 | 9 | | Имя | Тип | * | Описание | 10 | | --- | --- | --- | --- | 11 | | channel | String | | Имя канала, за который будет отвечать экземляр логгера. | 12 | 13 | #### Logger.ignores : Array.< String > 14 | 15 | Список игнорируемых каналов. 16 | 17 | #### Logger.logLevels : Array.< String > 18 | 19 | Список отображаемых в консоли уровней лога. 20 | 21 | #### Logger#debug 22 | 23 | Запись в лог с уровнем debug. 24 | 25 | #### Logger#log 26 | 27 | Запись в лог с уровнем log. 28 | 29 | #### Logger#info 30 | 31 | Запись в лог с уровнем info. 32 | 33 | #### Logger#warn 34 | 35 | Запись в лог с уровнем warn. 36 | 37 | #### Logger#error 38 | 39 | Запись в лог с уровнем error. 40 | 41 | #### Logger#trace 42 | 43 | Запись в лог с уровнем trace. 44 | 45 | ---- 46 | 47 | ### Методы 48 | 49 | #### Logger.log (level: String, channel: String, context: Object, args: *) 50 | 51 | Сделать запись в лог. 52 | 53 | | Имя | Тип | * | Описание | 54 | | --- | --- | --- | --- | 55 | | level | String | | Уровень лога. | 56 | | channel | String | | Канал. | 57 | | context | Object | | Контекст вызова. | 58 | | *\[args\]* | * | | Дополнительные аргументы. | 59 | 60 | -------------------------------------------------------------------------------- /spec/AbortablePromise.md: -------------------------------------------------------------------------------- 1 | ## *inner* *class* AbortablePromise 2 | 3 | Обещание с возможностью отмены связанного с ним действия. 4 | 5 | **Расширяет:** 6 | 7 | - [Promise](Promise.md#Promise) 8 | 9 | ---- 10 | 11 | ### Методы 12 | 13 | #### AbortablePromise#abort (reason: String \| Error) 14 | 15 | Отмена действия, связанного с обещанием. Абстрактный метод. 16 | 17 | | Имя | Тип | * | Описание | 18 | | --- | --- | --- | --- | 19 | | reason | String \| Error | | Причина отмены действия. | 20 | 21 | #### AbortablePromise#then (callback: function, errback: null \| function) : [Promise](Promise.md#Promise) *(inherits [Promise#then](Promise.md#Promise..then))* 22 | 23 | Назначить обработчики разрешения и отклонения обещания. 24 | 25 | | Имя | Тип | * | Описание | 26 | | --- | --- | --- | --- | 27 | | callback | function | | Обработчик успеха. | 28 | | *\[errback\]* | null \| function | | Обработчик ошибки. | 29 | 30 | > **Возвращает:** новое обещание из результатов обработчика. 31 | 32 | #### AbortablePromise#catch (errback: function) : [Promise](Promise.md#Promise) *(inherits [Promise#catch](Promise.md#Promise..catch))* 33 | 34 | Назначить обработчик отклонения обещания. 35 | 36 | | Имя | Тип | * | Описание | 37 | | --- | --- | --- | --- | 38 | | errback | function | | Обработчик ошибки. | 39 | 40 | > **Возвращает:** новое обещание из результатов обработчика. 41 | 42 | -------------------------------------------------------------------------------- /jsdoc/doc/render.gfm.js: -------------------------------------------------------------------------------- 1 | var cleanupTags = /<\/?(ul|li|p)>/g; 2 | var unescape = /\\(\{|\})/g; 3 | var beautify = /(\n[\t ]*){3,}/g; 4 | 5 | var trim = /^\s*|\s*$/g; 6 | var link = /\{@link (.*?) *\}/g; 7 | var linkhref = /\{@linkhref (.*?) ([^\}]*) *\}/g; 8 | var ltgt = /<(.*?)>/; 9 | 10 | module.exports = function(page, data) { 11 | page = page.replace(cleanupTags, "") 12 | .replace(unescape, "$1") 13 | .replace(/\r/g, "").replace(beautify, "\n\n"); 14 | 15 | page = page.replace(link, function(_, link) { 16 | if (ltgt.test(link)) { 17 | return link.replace(ltgt, function(_, parts) { 18 | return "< " + parts.split(",").map(function(types) { 19 | return types.split("|") 20 | .map(function(type) { return "{@link " + type.replace(trim, "") + "}" }) 21 | .join(" \\| "); 22 | }).join(", ") + " >"; 23 | }); 24 | } 25 | 26 | return "{@link " + link + "}"; 27 | }); 28 | 29 | page = page.replace(link, function(_, linkData) { 30 | linkData = linkData.split(" "); 31 | var linkPath = linkData.shift(); 32 | var linkName = linkData.shift() || linkPath; 33 | 34 | if (data.links[linkPath]) { 35 | return "[" + linkName + "](" + data.links[linkPath] + ")"; 36 | } else { 37 | return linkName; 38 | } 39 | }); 40 | 41 | page = page.replace(linkhref, "[$2]($1)"); 42 | 43 | return page; 44 | }; 45 | -------------------------------------------------------------------------------- /spec/volumeLib.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## *ns* volumeLib 4 | 5 | **Доступен извне как:** `ya.music.Audio.fx.volumeLib` 6 | 7 | Методы конвертации значений громкости. 8 | 9 | #### volumeLib.EPSILON : number 10 | 11 | Минимальное значение громкости, при котором происходит отключение звука. Ограничение в 0.01 подобрано эмпирически. 12 | 13 | ---- 14 | 15 | ### Методы 16 | 17 | #### volumeLib.toExponent (value: Number) : Number 18 | 19 | Вычисление значение относительной громкости по значению на логарифмической шкале. 20 | 21 | | Имя | Тип | * | Описание | 22 | | --- | --- | --- | --- | 23 | | value | Number | | Значение на шкале. | 24 | 25 | #### volumeLib.fromExponent (volume: Number) : Number 26 | 27 | Вычисление положения на логарифмической шкале по значению относительной громкости громкости 28 | 29 | | Имя | Тип | * | Описание | 30 | | --- | --- | --- | --- | 31 | | volume | Number | | Громкость. | 32 | 33 | #### volumeLib.toDBFS (volume: Number) : Number 34 | 35 | Вычисление значения dBFS из относительного значения громкости. 36 | 37 | | Имя | Тип | * | Описание | 38 | | --- | --- | --- | --- | 39 | | volume | Number | | Относительная громкость. | 40 | 41 | #### volumeLib.fromDBFS (dbfs: Number) : Number 42 | 43 | Вычисление значения относительной громкости из значения dBFS. 44 | 45 | | Имя | Тип | * | Описание | 46 | | --- | --- | --- | --- | 47 | | dbfs | Number | | Громкость в dBFS. | 48 | 49 | -------------------------------------------------------------------------------- /src/error/playback-error.js: -------------------------------------------------------------------------------- 1 | var ErrorClass = require('../lib/class/error-class'); 2 | 3 | /** 4 | * @exported ya.music.Audio.PlaybackError 5 | * @classdesc Класс ошибки воспроизведения. 6 | * @extends Error 7 | * @param {String} message Текст ошибки. 8 | * @param {String} src Ссылка на трек. 9 | * 10 | * @constructor 11 | */ 12 | var PlaybackError = function(message, src) { 13 | ErrorClass.call(this, message); 14 | 15 | this.src = src; 16 | }; 17 | 18 | PlaybackError.prototype = ErrorClass.create("PlaybackError"); 19 | 20 | /** 21 | * Отмена соединения. 22 | * @type {String} 23 | * @const 24 | */ 25 | PlaybackError.CONNECTION_ABORTED = "Connection aborted"; 26 | /** 27 | * Сетевая ошибка. 28 | * @type {String} 29 | * @const 30 | */ 31 | PlaybackError.NETWORK_ERROR = "Network error"; 32 | /** 33 | * Ошибка декодирования аудио. 34 | * @type {String} 35 | * @const 36 | */ 37 | PlaybackError.DECODE_ERROR = "Decode error"; 38 | /** 39 | * Недоступный источник. 40 | * @type {String} 41 | * @const 42 | */ 43 | PlaybackError.BAD_DATA = "Bad data"; 44 | 45 | /** 46 | * Не запускается воспроизведение. 47 | * @type {String} 48 | * @const 49 | */ 50 | PlaybackError.DONT_START = "Playback start error"; 51 | 52 | /** 53 | * Таблица соответствия кодов ошибок HTML5-плеера. 54 | * 55 | * @const 56 | * @type {Object} 57 | */ 58 | PlaybackError.html5 = { 59 | 1: PlaybackError.CONNECTION_ABORTED, 60 | 2: PlaybackError.NETWORK_ERROR, 61 | 3: PlaybackError.DECODE_ERROR, 62 | 4: PlaybackError.BAD_DATA 63 | }; 64 | 65 | //TODO: сделать классификатор ошибок Flash-плеера 66 | 67 | module.exports = PlaybackError; 68 | -------------------------------------------------------------------------------- /spec/config.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## *ns* config 4 | 5 | **Доступен извне как:** `ya.music.Audio.config` 6 | 7 | Настройки библиотеки. 8 | 9 | ---- 10 | 11 | ### Пространства имён 12 | 13 | ## *ns* config.audio 14 | 15 | Общие настройки. 16 | 17 | #### config.audio.retry : Number 18 | 19 | Количество попыток реинициализации 20 | 21 | ## *ns* config.flash 22 | 23 | Настройки подключения Flash-плеера. 24 | 25 | #### config.flash.path : String 26 | 27 | Путь к .swf файлу флеш-плеера 28 | 29 | #### config.flash.name : String 30 | 31 | Имя .swf файла флеш-плеера 32 | 33 | #### config.flash.version : String 34 | 35 | Минимальная версия флеш-плеера 36 | 37 | #### config.flash.playerID : String 38 | 39 | ID, который будет выставлен для элемента с Flash-плеером 40 | 41 | #### config.flash.callback : String 42 | 43 | Имя функции-обработчика событий Flash-плеера 44 | 45 | #### config.flash.initTimeout : Number 46 | 47 | Таймаут инициализации 48 | 49 | #### config.flash.loadTimeout : Number 50 | 51 | Таймаут загрузки 52 | 53 | #### config.flash.clickTimeout : Number 54 | 55 | Таймаут инициализации после клика 56 | 57 | #### config.flash.heartBeatInterval : Number 58 | 59 | Интервал проверки доступности Flash-плеера 60 | 61 | ## *ns* config.html5 62 | 63 | Описание настроек HTML5 плеера. 64 | 65 | #### config.html5.blacklist : Array.< String > 66 | 67 | Список идентификаторов для которых лучше не использовать html5 плеер. Используется при авто-определении типа плеера. Идентификаторы сравниваются со строкой построенной по шаблону @<platform.version> <platform.os>:<browser.name>/<browser.version> 68 | 69 | -------------------------------------------------------------------------------- /examples/quick-start/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-box-sizing: border-box; 3 | -moz-box-sizing: border-box; 4 | box-sizing: border-box; 5 | } 6 | 7 | .player { 8 | display: block; 9 | position: relative; 10 | width: 100%; 11 | height: 140px; 12 | } 13 | 14 | .overlay { 15 | display: block; 16 | position: absolute; 17 | top: 0; 18 | left: 0; 19 | width: 100%; 20 | height: 100%; 21 | z-index: 100; 22 | } 23 | 24 | .overlay_hidden { 25 | display: none; 26 | } 27 | 28 | .overlay_error { 29 | background: #ffcccc; 30 | color: #ff0000; 31 | } 32 | 33 | .controls_play { 34 | padding: 5px 10px; 35 | background: #fff; 36 | border: 1px #aaa solid; 37 | cursor: pointer; 38 | } 39 | 40 | .controls_play:hover { 41 | border-color: #666; 42 | } 43 | 44 | .player_playing .controls_play { 45 | background: #9DBAFF; 46 | } 47 | 48 | .progress { 49 | display: block; 50 | position: relative; 51 | width: 90%; 52 | height: 104px; 53 | border: 1px #ccc solid; 54 | margin: 10px 0; 55 | } 56 | 57 | .progress_loaded, .progress_current { 58 | display: block; 59 | position: absolute; 60 | height: 100%; 61 | top: 0; 62 | left: 0; 63 | } 64 | 65 | .progress_loaded { 66 | background: #ccc; 67 | z-index: 1; 68 | } 69 | 70 | .progress_current { 71 | background: #535BC3; 72 | z-index: 2; 73 | } 74 | 75 | .volume { 76 | display: block; 77 | position: absolute; 78 | right: 0; 79 | width: 9%; 80 | top: 0; 81 | height: 100%; 82 | border: 1px #ccc solid; 83 | } 84 | 85 | .volume_bar { 86 | display: block; 87 | position: absolute; 88 | bottom: 0; 89 | width: 100%; 90 | background: #535BC3; 91 | } 92 | -------------------------------------------------------------------------------- /src/fx/volume/volume-lib.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Методы конвертации значений громкости. 3 | * @name volumeLib 4 | * @exported ya.music.Audio.fx.volumeLib 5 | * @namespace 6 | */ 7 | var volumeLib = {}; 8 | 9 | /** 10 | * Минимальное значение громкости, при котором происходит отключение звука. 11 | * Ограничение в 0.01 подобрано эмпирически. 12 | * @type {number} 13 | */ 14 | volumeLib.EPSILON = 0.01; 15 | 16 | /** 17 | * Коэффициент для преобразований громкости из относительной шкалы в децибелы. 18 | * @type Number 19 | * @private 20 | */ 21 | volumeLib._DBFS_COEF = 20 / Math.log(10); 22 | 23 | /** 24 | * Вычисление значение относительной громкости по значению на логарифмической шкале. 25 | * @param {Number} value Значение на шкале. 26 | * @returns {Number} 27 | */ 28 | volumeLib.toExponent = function(value) { 29 | var volume = Math.pow(volumeLib.EPSILON, 1 - value); 30 | return volume > volumeLib.EPSILON ? volume : 0; 31 | }; 32 | 33 | /** 34 | * Вычисление положения на логарифмической шкале по значению относительной громкости громкости. 35 | * @param {Number} volume Громкость. 36 | * @returns {Number} 37 | */ 38 | volumeLib.fromExponent = function(volume) { 39 | return 1 - Math.log(Math.max(volume, volumeLib.EPSILON)) / Math.log(volumeLib.EPSILON); 40 | }; 41 | 42 | /** 43 | * Вычисление значения dBFS из относительного значения громкости. 44 | * @param {Number} volume Относительная громкость. 45 | * @returns {Number} 46 | */ 47 | volumeLib.toDBFS = function(volume) { 48 | return Math.log(volume) * volumeLib._DBFS_COEF; 49 | }; 50 | 51 | /** 52 | * Вычисление значения относительной громкости из значения dBFS. 53 | * @param {Number} dbfs Громкость в dBFS. 54 | * @returns {Number} 55 | */ 56 | volumeLib.fromDBFS = function(dbfs) { 57 | return Math.exp(dbfs / volumeLib._DBFS_COEF); 58 | }; 59 | 60 | module.exports = volumeLib; 61 | -------------------------------------------------------------------------------- /src/fx/equalizer/equalizer-band.js: -------------------------------------------------------------------------------- 1 | var Events = require('../../lib/async/events'); 2 | var EqualizerStatic = require('./equalizer-static'); 3 | 4 | // ================================================================= 5 | 6 | // Конструктор 7 | 8 | // ================================================================= 9 | 10 | /** 11 | * Событие изменения значения усиления. 12 | * @event EqualizerBand.EVENT_CHANGE 13 | * @param {Number} value Новое значение. 14 | */ 15 | 16 | /** 17 | * @classdesc Полоса пропускания эквалайзера. 18 | * @extends Events 19 | * 20 | * @param {AudioContext} audioContext Контекст Web Audio API. 21 | * @param {String} type Тип фильтра. 22 | * @param {Number} frequency Частота фильтра. 23 | * 24 | * @fires EqualizerBand.EVENT_CHANGE 25 | * 26 | * @constructor 27 | * @private 28 | */ 29 | var EqualizerBand = function(audioContext, type, frequency) { 30 | Events.call(this); 31 | 32 | this.type = type; 33 | 34 | this.filter = audioContext.createBiquadFilter(); 35 | this.filter.type = type; 36 | this.filter.frequency.value = frequency; 37 | this.filter.Q.value = 1; 38 | this.filter.gain.value = 0; 39 | }; 40 | Events.mixin(EqualizerBand); 41 | 42 | // ================================================================= 43 | 44 | // Управление настройками 45 | 46 | // ================================================================= 47 | 48 | /** 49 | * Получить частоту полосы пропускания. 50 | * @returns {Number} 51 | */ 52 | EqualizerBand.prototype.getFreq = function() { 53 | return this.filter.frequency.value; 54 | }; 55 | 56 | /** 57 | * Получить значение усиления. 58 | * @returns {Number} 59 | */ 60 | EqualizerBand.prototype.getValue = function() { 61 | return this.filter.gain.value; 62 | }; 63 | 64 | /** 65 | * Установить значение усиления. 66 | * @param {Number} value Значение. 67 | */ 68 | EqualizerBand.prototype.setValue = function(value) { 69 | this.filter.gain.value = value; 70 | this.trigger(EqualizerStatic.EVENT_CHANGE, value); 71 | }; 72 | 73 | module.exports = EqualizerBand; 74 | -------------------------------------------------------------------------------- /src/error/audio-error.js: -------------------------------------------------------------------------------- 1 | var ErrorClass = require('../lib/class/error-class'); 2 | 3 | /** 4 | * @exported ya.music.Audio.AudioError 5 | * @classdesc Класс ошибки аудиопллеера. 6 | * @extends Error 7 | * @param {String} message Текст ошибки. 8 | * 9 | * @constructor 10 | */ 11 | var AudioError = function(message) { 12 | ErrorClass.call(this, message); 13 | }; 14 | AudioError.prototype = ErrorClass.create("AudioError"); 15 | 16 | /** 17 | * Не найдена реализация плеера или возникла ошибка при инициализации всех доступных реализаций. 18 | * @type {String} 19 | * @const 20 | */ 21 | AudioError.NO_IMPLEMENTATION = "cannot find suitable implementation"; 22 | /** 23 | * Аудиофайл не был предзагружен или во время загрузки произошла ошибка. 24 | * @type {String} 25 | * @const 26 | */ 27 | AudioError.NOT_PRELOADED = "track is not preloaded"; 28 | /** 29 | * Действие недоступно из текущего состояния. 30 | * @type {String} 31 | * @const 32 | */ 33 | AudioError.BAD_STATE = "action is not permited from current state"; 34 | 35 | /** 36 | * Flash-плеер был заблокирован. 37 | * @type {String} 38 | * @const 39 | */ 40 | AudioError.FLASH_BLOCKER = "flash is rejected by flash blocker plugin"; 41 | /** 42 | * Возникла ошибка при инициализации Flash-плеера по неизвестным причинам. 43 | * @type {String} 44 | * @const 45 | */ 46 | AudioError.FLASH_UNKNOWN_CRASH = "flash is crashed without reason"; 47 | /** 48 | * Возникла ошибка при инициализации Flash-плеера из-за таймаута. 49 | * @type {String} 50 | * @const 51 | */ 52 | AudioError.FLASH_INIT_TIMEOUT = "flash init timed out"; 53 | /** 54 | * Внутренняя ошибка Flash-плеера. 55 | * @type {String} 56 | * @const 57 | */ 58 | AudioError.FLASH_INTERNAL_ERROR = "flash internal error"; 59 | /** 60 | * Попытка вызвать недоступный экземляр Flash-плеера. 61 | * @type {String} 62 | * @const 63 | */ 64 | AudioError.FLASH_EMMITER_NOT_FOUND = "flash event emmiter not found"; 65 | /** 66 | * Flash-плеер перестал отвечать на запросы. 67 | * @type {String} 68 | * @const 69 | */ 70 | AudioError.FLASH_NOT_RESPONDING = "flash player doesn't response"; 71 | 72 | module.exports = AudioError; 73 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | var BUILDDIR = "./dist"; 4 | 5 | // Project configuration. 6 | grunt.initConfig({ 7 | pkg: grunt.file.readJSON('package.json'), 8 | 9 | clean: [BUILDDIR], 10 | 11 | mkdir: { 12 | options: { 13 | create: [BUILDDIR] 14 | } 15 | }, 16 | 17 | browserify: { 18 | options: { 19 | debug: true 20 | }, 21 | main: { 22 | src: "./src/index.js", 23 | dest: BUILDDIR + "/index.js" 24 | }, 25 | modules: { 26 | src: "./src/modules.js", 27 | dest: BUILDDIR + "/modules.js" 28 | } 29 | }, 30 | 31 | uglify: { 32 | options: { 33 | mangle: true, 34 | compress: true, 35 | bareReturns: true, 36 | stats: true 37 | }, 38 | main: { 39 | src: BUILDDIR + '/index.js', 40 | dest: BUILDDIR + '/index.min.js', 41 | options: { 42 | sourceMap: true, 43 | sourceMapName: BUILDDIR + "/index.map.json" 44 | } 45 | }, 46 | modules: { 47 | src: BUILDDIR + '/modules.js', 48 | dest: BUILDDIR + '/modules.min.js', 49 | options: { 50 | sourceMap: true, 51 | sourceMapName: BUILDDIR + "/modules.map.json" 52 | } 53 | } 54 | }, 55 | 56 | copy: { 57 | flash: { 58 | src: "./src/flash/build/player-2_0.swf", 59 | dest: BUILDDIR + "/player-2_0.swf" 60 | } 61 | } 62 | }); 63 | 64 | grunt.loadNpmTasks('grunt-contrib-clean'); 65 | grunt.loadNpmTasks('grunt-mkdir'); 66 | grunt.loadNpmTasks('grunt-browserify'); 67 | grunt.loadNpmTasks('grunt-contrib-uglify'); 68 | grunt.loadNpmTasks('grunt-contrib-copy'); 69 | 70 | grunt.registerTask('build', ['clean', 'mkdir', 'browserify', 'copy']); 71 | grunt.registerTask('all', ['build', 'uglify']); 72 | 73 | // Default task. 74 | grunt.registerTask('default', ['all']); 75 | }; 76 | -------------------------------------------------------------------------------- /jsdoc/doc/render.jsdoc.js: -------------------------------------------------------------------------------- 1 | var cleanupTags = /<\/?(ul|li|p)>/g; 2 | var bbcodes = /\{\[(.*?)\]\}/g; 3 | var unescape = /\\(\{|\})/g; 4 | var beautify_lines = /(\n[\t ]*){3,}/g; 5 | var beautify_asterix = /([\t ]*\*[\t ]*\n){2,}/g; 6 | var linkhref = /\{@linkhref (.*?) ([^\}]*) *\}/g; 7 | 8 | var doclet = /\/\*\*[\s\S]*?\*\//g; 9 | 10 | module.exports = function(page, data, style) { 11 | page = page.replace(cleanupTags, "") 12 | .replace(unescape, "$1") 13 | .replace(bbcodes, "<$1>"); 14 | 15 | if (style === 'tech') { 16 | page = page.replace(linkhref, "$2"); 17 | } else { 18 | page = page.replace(linkhref, "{@link $1 $2}"); 19 | } 20 | 21 | var aliases = []; 22 | 23 | page = page.replace(doclet, function(doclet) { 24 | var alias = /@alias (.*)/g; 25 | var name = /@name (.*)/g; 26 | 27 | var rawName; 28 | if (rawName = name.exec(doclet)) { 29 | rawName = rawName[1]; 30 | 31 | doclet = doclet.replace(alias, function(_, aliasName) { 32 | aliases.push({ 33 | reg: new RegExp("([^\\w.#~\/])" + rawName + "([^\\w])", "g"), 34 | rep: "$1" + aliasName + "$2" 35 | // rep: aliasName 36 | }); 37 | 38 | return ""; 39 | }); 40 | } 41 | 42 | return doclet; 43 | }); 44 | 45 | // console.log("-----------------------------"); 46 | // console.log(aliases); 47 | 48 | aliases.forEach(function(alias) { 49 | page = page.replace(alias.reg, alias.rep); 50 | }); 51 | 52 | if (style === "tech") { 53 | page = page 54 | .replace(/\.<([^/]*?)>/g, ".<$1>") 55 | .replace(/<(\/?)code>/g, "<$1codeph>") 56 | .replace(/<(\/?)strong>/g, "<$1b>") 57 | .replace(/ya\.music\./g, "") 58 | .replace(/Array\.<([^/]*?)>/g, "$1[]") 59 | .replace(/ Error( |$)/mg, 60 | " Error "); 61 | } 62 | 63 | return page.replace(/\r/g, "").replace(beautify_lines, "\n\n") 64 | .replace(beautify_asterix, " *\n"); 65 | }; 66 | -------------------------------------------------------------------------------- /src/lib/async/promise.js: -------------------------------------------------------------------------------- 1 | var vow = require('vow'); 2 | var detect = require('../browser/detect'); 3 | var merge = require('../data/merge'); 4 | 5 | // ================================================================= 6 | 7 | // Promise 8 | 9 | // ================================================================= 10 | 11 | /** 12 | * @classdesc Обещание по спецификации {@linkhref https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Promise ES 2015 promises}. В устаревших браузерах и IE используется замена из библиотеки {@linkhref http://github.com/dfilatov/vow.git vow} 13 | * 14 | * @exported ya.music.lib.Promise 15 | * @constructor 16 | */ 17 | var Promise; 18 | if (typeof window.Promise !== "function" 19 | || detect.browser.name === "msie" || detect.browser.name === "edge" // мелкие мягкие как всегда ничего не умеют делать правильно 20 | ) { 21 | Promise = function(resolver) { 22 | var promise; 23 | try { 24 | promise = new vow.Promise(resolver); 25 | } catch(e) { 26 | promise = vow.reject(e); 27 | } 28 | return promise; 29 | }; 30 | merge(Promise, vow.Promise, true); 31 | Promise.prototype = vow.Promise.prototype; 32 | } else { 33 | Promise = window.Promise; 34 | } 35 | 36 | module.exports = Promise; 37 | 38 | /** 39 | * Назначить обработчики разрешения и отклонения обещания. 40 | * @method Promise#then 41 | * @param {function} callback Обработчик успеха. 42 | * @param {null|function} [errback] Обработчик ошибки. 43 | * @returns {Promise} новое обещание из результатов обработчика. 44 | */ 45 | 46 | /** 47 | * Назначить обработчик отклонения обещания. 48 | * @method Promise#catch 49 | * @param {function} errback Обработчик ошибки. 50 | * @returns {Promise} новое обещание из результатов обработчика. 51 | */ 52 | 53 | // ================================================================= 54 | 55 | // AbortablePromise 56 | 57 | // ================================================================= 58 | 59 | /** 60 | * @class AbortablePromise 61 | * @classdesc Обещание с возможностью отмены связанного с ним действия. 62 | * @extends Promise 63 | */ 64 | 65 | /** 66 | * Отмена действия, связанного с обещанием. Абстрактный метод. 67 | * @method AbortablePromise#abort 68 | * @param {String|Error} reason Причина отмены действия. 69 | * @abstract 70 | */ 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/flash/loader.js: -------------------------------------------------------------------------------- 1 | var FlashBlockNotifier = require('./flashblocknotifier'); 2 | var FlashEmbedder = require('./flashembedder'); 3 | var detect = require('../lib/browser/detect'); 4 | 5 | var winSafari = detect.platform.os === 'windows' && detect.browser.name === 'safari'; 6 | 7 | var CONTAINER_CLASS = "ya-flash-player-wrapper"; 8 | 9 | /** 10 | * Загрузчик флеш-плеера 11 | * 12 | * @alias FlashManager~flashLoader 13 | * 14 | * @param {string} url - Ссылка на плеера 15 | * @param {string} minVersion - минимальная версия плеера 16 | * @param {string|number} id - идентификатор нового плеера 17 | * @param {function} loadCallback - колбек для события загрузки 18 | * @param {object} flashVars - данные передаваемые во флеш 19 | * @param {HTMLElement} container - контейнер для видимого флеш-плеера 20 | * @param {string} sizeX - размер по горизонтали 21 | * @param {string} sizeY - размер по вертикали 22 | * 23 | * @private 24 | * 25 | * @returns {HTMLElement} -- Контейнер флеш-плеера 26 | */ 27 | module.exports = function(url, minVersion, id, loadCallback, flashVars, container, sizeX, sizeY) { 28 | var $flashPlayer = document.createElement("div"); 29 | $flashPlayer.id = "wrapper_" + id; 30 | $flashPlayer.innerHTML = '
'; 31 | 32 | sizeX = sizeX || "1000"; 33 | sizeY = sizeY || "1000"; 34 | 35 | var embedder, 36 | flashSizeX, 37 | flashSizeY, 38 | options; 39 | 40 | if (container && !winSafari) { 41 | embedder = FlashEmbedder; 42 | flashSizeX = sizeX; 43 | flashSizeY = sizeY; 44 | options = {allowscriptaccess: "always", wmode: "transparent"}; 45 | 46 | $flashPlayer.className = CONTAINER_CLASS; 47 | $flashPlayer.style.cssText = 'position: relative; width: 100%; height: 100%; overflow: hidden;'; 48 | container.appendChild($flashPlayer); 49 | } else { 50 | embedder = FlashBlockNotifier; 51 | flashSizeX = flashSizeY = "1"; 52 | options = {allowscriptaccess: "always"}; 53 | 54 | $flashPlayer.style.cssText = 'position: absolute; left: -1px; top: -1px; width: 0px; height: 0px; overflow: hidden;'; 55 | document.body.appendChild($flashPlayer); 56 | } 57 | 58 | embedder.embedSWF( 59 | url, 60 | id, 61 | flashSizeX, 62 | flashSizeY, 63 | minVersion, 64 | "", 65 | flashVars, 66 | options, 67 | {}, 68 | loadCallback 69 | ); 70 | 71 | return $flashPlayer; 72 | }; 73 | -------------------------------------------------------------------------------- /spec/Events.md: -------------------------------------------------------------------------------- 1 | ## *class* Events 2 | 3 | **Доступен извне как:** `ya.music.lib.Events` 4 | 5 | Диспетчер событий. 6 | 7 | #### new Events() 8 | 9 | ---- 10 | 11 | ### Методы 12 | 13 | #### Events.mixin (classConstructor: function) : function 14 | 15 | Расширить произвольный класс свойствами диспетчера событий. 16 | 17 | | Имя | Тип | * | Описание | 18 | | --- | --- | --- | --- | 19 | | classConstructor | function | | Конструктор класса. | 20 | 21 | > **Возвращает:** тот же конструктор класса, расширенный свойствами диспетчера событий. 22 | 23 | #### Events.eventize (object: Object) : Object 24 | 25 | Расширить произвольный объект свойствами диспетчера событий. 26 | 27 | | Имя | Тип | * | Описание | 28 | | --- | --- | --- | --- | 29 | | object | Object | | Объект. | 30 | 31 | > **Возвращает:** тот же объект, расширенный свойствами диспетчера событий. 32 | 33 | #### Events#on (event: String, callback: function) : [Events](Events.md#Events) 34 | 35 | Подписаться на событие (цепочный метод). 36 | 37 | | Имя | Тип | * | Описание | 38 | | --- | --- | --- | --- | 39 | | event | String | | Имя события. | 40 | | callback | function | | Обработчик события. | 41 | 42 | > **Возвращает:** ссылку на контекст. 43 | 44 | #### Events#off (event: String, callback: function) : [Events](Events.md#Events) 45 | 46 | Отписаться от события (цепочный метод). 47 | 48 | | Имя | Тип | * | Описание | 49 | | --- | --- | --- | --- | 50 | | event | String | | Имя события. | 51 | | callback | function | | Обработчик события. | 52 | 53 | > **Возвращает:** ссылку на контекст. 54 | 55 | #### Events#once (event: String, callback: function) : [Events](Events.md#Events) 56 | 57 | Подписаться на событие и отписаться сразу после его первого возникновения (цепочный метод). 58 | 59 | | Имя | Тип | * | Описание | 60 | | --- | --- | --- | --- | 61 | | event | String | | Имя события. | 62 | | callback | function | | Обработчик события. | 63 | 64 | > **Возвращает:** ссылку на контекст. 65 | 66 | #### Events#clearListeners () : [Events](Events.md#Events) 67 | 68 | Отписаться от всех слушателей событий (цепочный метод). 69 | 70 | > **Возвращает:** ссылку на контекст. 71 | 72 | #### Events#muteEvents () : [Events](Events.md#Events) 73 | 74 | Остановить запуск событий (цепочный метод). 75 | 76 | > **Возвращает:** ссылку на контекст. 77 | 78 | #### Events#unmuteEvents () : [Events](Events.md#Events) 79 | 80 | Возобновить запуск событий (цепочный метод). 81 | 82 | > **Возвращает:** ссылку на контекст. 83 | 84 | -------------------------------------------------------------------------------- /src/audio-static.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @alias Audio 3 | * @ignore 4 | */ 5 | var AudioStatic = {}; 6 | 7 | /** 8 | * Начало воспроизведения трека. 9 | * @type {String} 10 | * @const 11 | * @ignore 12 | */ 13 | AudioStatic.EVENT_PLAY = "play"; 14 | /** 15 | * Остановка воспроизведения. 16 | * @type {String} 17 | * @const 18 | * @ignore 19 | */ 20 | AudioStatic.EVENT_STOP = "stop"; 21 | /** 22 | * Пауза воспроизведения. 23 | * @type {String} 24 | * @const 25 | * @ignore 26 | */ 27 | AudioStatic.EVENT_PAUSE = "pause"; 28 | /** 29 | * Обновление позиции воспроизведения. 30 | * @type {String} 31 | * @const 32 | * @ignore 33 | */ 34 | AudioStatic.EVENT_PROGRESS = "progress"; 35 | /** 36 | * Началась загрузка трека. 37 | * @type {String} 38 | * @const 39 | * @ignore 40 | */ 41 | AudioStatic.EVENT_LOADING = "loading"; 42 | /** 43 | * Загрузка трека завершена. 44 | * @type {String} 45 | * @const 46 | * @ignore 47 | */ 48 | AudioStatic.EVENT_LOADED = "loaded"; 49 | /** 50 | * Изменение громкости. 51 | * @type {String} 52 | * @const 53 | * @ignore 54 | */ 55 | AudioStatic.EVENT_VOLUME = "volumechange"; 56 | 57 | /** 58 | * Воспроизведение трека завершено. 59 | * @type {String} 60 | * @const 61 | * @ignore 62 | */ 63 | AudioStatic.EVENT_ENDED = "ended"; 64 | /** 65 | * Возникла ошибка при инициализации плеера. 66 | * @type {String} 67 | * @const 68 | * @ignore 69 | */ 70 | AudioStatic.EVENT_CRASHED = "crashed"; 71 | /** 72 | * Возникла ошибка при воспроизведении. 73 | * @type {String} 74 | * @const 75 | * @ignore 76 | */ 77 | AudioStatic.EVENT_ERROR = "error"; 78 | /** 79 | * Изменение статуса плеера. 80 | * @type {String} 81 | * @const 82 | * @ignore 83 | */ 84 | AudioStatic.EVENT_STATE = "state"; 85 | /** 86 | * Переключение между текущим и предзагруженным треком. 87 | * @type {String} 88 | * @const 89 | * @ignore 90 | */ 91 | AudioStatic.EVENT_SWAP = "swap"; 92 | /** 93 | * Событие предзагрузчика. Используется в качестве префикса. 94 | * @type {String} 95 | * @const 96 | */ 97 | AudioStatic.PRELOADER_EVENT = "preloader:"; 98 | /** 99 | * Плеер находится в состоянии инициализации. 100 | * @type {String} 101 | * @const 102 | */ 103 | AudioStatic.STATE_INIT = "init"; 104 | /** 105 | * Не удалось инициализировать плеер. 106 | * @type {String} 107 | * @const 108 | */ 109 | AudioStatic.STATE_CRASHED = "crashed"; 110 | /** 111 | * Плеер готов и ожидает. 112 | * @type {String} 113 | * @const 114 | */ 115 | AudioStatic.STATE_IDLE = "idle"; 116 | /** 117 | * Плеер проигрывает трек. 118 | * @type {String} 119 | * @const 120 | */ 121 | AudioStatic.STATE_PLAYING = "playing"; 122 | /** 123 | * Плеер поставлен на паузу. 124 | * @type {String} 125 | * @const 126 | */ 127 | AudioStatic.STATE_PAUSED = "paused"; 128 | 129 | module.exports = AudioStatic; 130 | -------------------------------------------------------------------------------- /src/fx/equalizer/default.presets.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | "id": "default", 4 | "preamp": 0, 5 | "bands": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 6 | }, 7 | { 8 | "id": "Classical", 9 | "preamp": -0.5, 10 | "bands": [-0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -3.5, -3.5, -3.5, -4.5] 11 | }, 12 | { 13 | "id": "Club", 14 | "preamp": -3.359999895095825, 15 | "bands": [-0.5, -0.5, 4, 2.5, 2.5, 2.5, 1.5, -0.5, -0.5, -0.5] 16 | }, 17 | { 18 | "id": "Dance", 19 | "preamp": -2.1599998474121094, 20 | "bands": [4.5, 3.5, 1, -0.5, -0.5, -2.5, -3.5, -3.5, -0.5, -0.5] 21 | }, 22 | { 23 | "id": "Full Bass", 24 | "preamp": -3.5999999046325684, 25 | "bands": [4, 4.5, 4.5, 2.5, 0.5, -2, -4, -5, -5.5, -5.5] 26 | }, 27 | { 28 | "id": "Full Bass & Treble", 29 | "preamp": -5.039999961853027, 30 | "bands": [3.5, 2.5, -0.5, -3.5, -2, 0.5, 4, 5.5, 6, 6] 31 | }, 32 | { 33 | "id": "Full Treble", 34 | "preamp": -6, 35 | "bands": [-4.5, -4.5, -4.5, -2, 1, 5.5, 8, 8, 8, 8] 36 | }, 37 | { 38 | "id": "Laptop Speakers / Headphone", 39 | "preamp": -4.079999923706055, 40 | "bands": [2, 5.5, 2.5, -1.5, -1, 0.5, 2, 4.5, 6, 7] 41 | }, 42 | { 43 | "id": "Large Hall", 44 | "preamp": -3.5999999046325684, 45 | "bands": [5, 5, 2.5, 2.5, -0.5, -2, -2, -2, -0.5, -0.5] 46 | }, 47 | { 48 | "id": "Live", 49 | "preamp": -2.6399998664855957, 50 | "bands": [-2, -0.5, 2, 2.5, 2.5, 2.5, 2, 1, 1, 1] 51 | }, 52 | { 53 | "id": "Party", 54 | "preamp": -2.6399998664855957, 55 | "bands": [3.5, 3.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 3.5, 3.5] 56 | }, 57 | { 58 | "id": "Pop", 59 | "preamp": -3.119999885559082, 60 | "bands": [-0.5, 2, 3.5, 4, 2.5, -0.5, -1, -1, -0.5, -0.5] 61 | }, 62 | { 63 | "id": "Reggae", 64 | "preamp": -4.079999923706055, 65 | "bands": [-0.5, -0.5, -0.5, -2.5, -0.5, 3, 3, -0.5, -0.5, -0.5] 66 | }, 67 | { 68 | "id": "Rock", 69 | "preamp": -5.039999961853027, 70 | "bands": [4, 2, -2.5, -4, -1.5, 2, 4, 5.5, 5.5, 5.5] 71 | }, 72 | { 73 | "id": "Ska", 74 | "preamp": -5.519999980926514, 75 | "bands": [-1, -2, -2, -0.5, 2, 2.5, 4, 4.5, 5.5, 4.5] 76 | }, 77 | { 78 | "id": "Soft", 79 | "preamp": -4.799999713897705, 80 | "bands": [2, 0.5, -0.5, -1, -0.5, 2, 4, 4.5, 5.5, 6] 81 | }, 82 | { 83 | "id": "Soft Rock", 84 | "preamp": -2.6399998664855957, 85 | "bands": [2, 2, 1, -0.5, -2, -2.5, -1.5, -0.5, 1, 4] 86 | }, 87 | { 88 | "id": "Techno", 89 | "preamp": -3.8399999141693115, 90 | "bands": [4, 2.5, -0.5, -2.5, -2, -0.5, 4, 4.5, 4.5, 4] 91 | } 92 | ]; 93 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Настройки библиотеки. 3 | * @exported ya.music.Audio.config 4 | * @namespace 5 | */ 6 | var config = { 7 | 8 | // ================================================================= 9 | 10 | // Общие настройки 11 | 12 | // ================================================================= 13 | 14 | /** 15 | * Общие настройки. 16 | * @namespace 17 | */ 18 | audio: { 19 | /** 20 | * Количество попыток реинициализации. 21 | * @type {Number} 22 | */ 23 | retry: 3 24 | }, 25 | 26 | // ================================================================= 27 | 28 | // Flash-плеер 29 | 30 | // ================================================================= 31 | 32 | /** 33 | * Настройки подключения Flash-плеера. 34 | * @namespace 35 | */ 36 | flash: { 37 | /** 38 | * Путь к .swf файлу флеш-плеера. 39 | * @type {String} 40 | */ 41 | path: "dist", 42 | /** 43 | * Имя .swf файла флеш-плеера. 44 | * @type {String} 45 | */ 46 | name: "player-2_1.swf", 47 | /** 48 | * Минимальная версия флеш-плеера. 49 | * @type {String} 50 | */ 51 | version: "9.0.28", 52 | /** 53 | * ID, который будет выставлен для элемента с Flash-плеером. 54 | * @type {String} 55 | */ 56 | playerID: "YandexAudioFlashPlayer", 57 | /** 58 | * Имя функции-обработчика событий Flash-плеера. 59 | * @type {String} 60 | * @const 61 | */ 62 | callback: "ya.music.Audio._flashCallback", 63 | /** 64 | * Таймаут инициализации. 65 | * @type {Number} 66 | */ 67 | initTimeout: 3000, // 3 sec 68 | /** 69 | * Таймаут загрузки 70 | * @type {Number} 71 | */ 72 | loadTimeout: 5000, 73 | /** 74 | * Таймаут инициализации после клика. 75 | * @type {Number} 76 | */ 77 | clickTimeout: 1000, 78 | /** 79 | * Интервал проверки доступности Flash-плеера. 80 | * @type {Number} 81 | */ 82 | heartBeatInterval: 1000 83 | }, 84 | 85 | // ================================================================= 86 | 87 | // HTML5-плеер 88 | 89 | // ================================================================= 90 | 91 | /** 92 | * Описание настроек HTML5 плеера. 93 | * @namespace 94 | */ 95 | html5: { 96 | /** 97 | * Список идентификаторов для которых лучше не использовать html5 плеер. Используется при 98 | * авто-определении типа плеера. Идентификаторы сравниваются со строкой построенной по шаблону 99 | * `@ :/` 100 | * 101 | * @type { Array. } 102 | */ 103 | blacklist: ["linux:mozilla", "unix:mozilla", "macos:mozilla", ":opera", "@NT 5", "@NT 4", ":msie/9"] 104 | } 105 | }; 106 | 107 | module.exports = config; 108 | -------------------------------------------------------------------------------- /src/flash/src/AudioLoader.as: -------------------------------------------------------------------------------- 1 | package { 2 | import flash.events.Event; 3 | import flash.events.EventDispatcher; 4 | import flash.events.IOErrorEvent; 5 | import flash.events.ProgressEvent; 6 | import flash.media.Sound; 7 | import flash.net.URLRequest; 8 | 9 | public final class AudioLoader extends EventDispatcher { 10 | public var sound:Sound; 11 | 12 | private var _duration:int = 0; 13 | private var guessDuration:int = -1; 14 | public var loaded:int = 0; 15 | 16 | public var isLoading:Boolean = false; 17 | public var isLoaded:Boolean = false; 18 | public var preloaded:String = ""; 19 | public var src:String = ""; 20 | 21 | public function AudioLoader() { 22 | } 23 | 24 | public function get duration():int { 25 | if (this._duration > 0) { 26 | return this._duration 27 | } else { 28 | return this.guessDuration; 29 | } 30 | } 31 | 32 | public function load(src:String, duration:Number):void { 33 | this.abort(); 34 | 35 | this.sound = new Sound(); 36 | this._duration = duration; 37 | 38 | this.sound.addEventListener(Event.OPEN, this.onLoadingStart); 39 | this.sound.addEventListener(Event.COMPLETE, this.onLoadingEnd); 40 | this.sound.addEventListener(ProgressEvent.PROGRESS, this.onLoadingProgress); 41 | this.sound.addEventListener(IOErrorEvent.IO_ERROR, this.onError); 42 | 43 | this.sound.load(new URLRequest(src)); 44 | this.src = src; 45 | } 46 | 47 | public function abort():void { 48 | if (!(this.sound is Sound)) { 49 | return; 50 | } 51 | 52 | this.sound.removeEventListener(Event.OPEN, this.onLoadingStart); 53 | this.sound.removeEventListener(Event.COMPLETE, this.onLoadingEnd); 54 | this.sound.removeEventListener(ProgressEvent.PROGRESS, this.onLoadingProgress); 55 | this.sound.removeEventListener(IOErrorEvent.IO_ERROR, this.onError); 56 | 57 | try { 58 | this.sound.close(); 59 | } catch (e:Error) { 60 | } 61 | this.sound = null; 62 | 63 | this._duration = 0; 64 | this.loaded = 0; 65 | this.isLoaded = false; 66 | this.isLoading = false; 67 | } 68 | 69 | private function onLoadingStart(event:Event):void { 70 | this.isLoading = true; 71 | this.dispatchEvent(new Event(AudioEvent.EVENT_LOADING)); 72 | } 73 | 74 | private function onLoadingEnd(event:Event):void { 75 | this._duration = this.sound.length; 76 | this.loaded = this.duration; 77 | this.isLoaded = true; 78 | 79 | this.dispatchEvent(new Event(AudioEvent.EVENT_PROGRESS)); 80 | this.dispatchEvent(new Event(AudioEvent.EVENT_LOADED)); 81 | } 82 | 83 | private function onLoadingProgress(event:ProgressEvent):void { 84 | //FIXME: it will give wrong values on VBR audio (Variable BitRate), and it gives imprecise values with CBR audio 85 | this.guessDuration = this.sound.length * event.bytesTotal / event.bytesLoaded; 86 | 87 | this.loaded = this.duration * event.bytesLoaded / event.bytesTotal; 88 | this.dispatchEvent(new Event(AudioEvent.EVENT_PROGRESS)); 89 | } 90 | 91 | private function onError(event:IOErrorEvent):void { 92 | this.abort(); 93 | this.dispatchEvent(event); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /jsdoc/doc/render.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | var Handlebars = require("handlebars"); 3 | var renderType; 4 | var renderStyle; 5 | var files; 6 | 7 | var layout; 8 | var index; 9 | var parser; 10 | 11 | var trim = /^\s*|\s*$|\n(\{\{\/if}})|(\{\{#if [.\w ]*}})\n|\n(\{\{else[.\w ]*}})\n/g; 12 | 13 | var prepare = function(path, style) { 14 | renderType = style.split("-")[0]; 15 | renderStyle = style.split("-")[1]; 16 | files = renderStyle === "files"; 17 | 18 | var partials = fs.readdirSync(path + "/" + style); 19 | 20 | partials.forEach(function(partial) { 21 | Handlebars.registerPartial(partial.replace(".hbs", ""), 22 | fs.readFileSync(path + "/" + style + "/" + partial, {encoding: "utf8"}).replace(trim, "$1$2$3") 23 | ); 24 | }); 25 | 26 | layout = Handlebars.compile( 27 | fs.readFileSync(path + "/" + style + "/layout.hbs", {encoding: "utf8"}).replace(trim, "$1$2$3") 28 | ); 29 | 30 | try { 31 | index = Handlebars.compile( 32 | fs.readFileSync(path + "/" + style + "/index.hbs", {encoding: "utf8"}).replace(trim, "$1$2$3") 33 | ); 34 | } catch(e) {} 35 | 36 | parser = require("./render." + renderType + ".js"); 37 | }; 38 | 39 | var render = function(data, out) { 40 | var renderPage = function(item) { 41 | var page = {}; 42 | page[item.kind] = item; 43 | return parser(layout(page), data, item); 44 | }; 45 | 46 | var list = {}; 47 | 48 | if (files) { 49 | for (var link in data.links) { 50 | var item = data.links[link]; 51 | var kind = item.kind; 52 | var parent = item.parent; 53 | var parentKind = parent && parent.kind; 54 | var path; 55 | 56 | if (parent && (parentKind === "class" || parentKind === "typedef" || parentKind === "namespace")) { 57 | path = parent.name; 58 | } else if (kind === "class" || kind === "typedef" || kind === "namespace") { 59 | path = item.name; 60 | } else { 61 | path = "global"; 62 | } 63 | 64 | data.links[link] = path + ".md#" + link.replace(/#/g, "..").replace(/~/g, "--"); 65 | } 66 | 67 | var makeFile = function(item) { list[item.name] = renderPage(item); }; 68 | 69 | // кладём все классы в отдельные файлы даже если они внутренние 70 | data.linear["class"].forEach(makeFile); 71 | // кладём только глобальные тайпдефы в отдельные файлы 72 | data.tree["typedef"].forEach(makeFile); 73 | // кладём только глобальные неймспейсы в отдельные файлы 74 | data.tree["namespace"].forEach(makeFile); 75 | 76 | list.global = ""; 77 | 78 | for (var key in data.linear) { 79 | if (key === "class" || key === "typedef" || key === "namespace") { 80 | continue; 81 | } 82 | 83 | list.global += data.linear[key].map(renderPage).join("\n\n"); 84 | } 85 | 86 | list[out] = parser(index(data), data, renderStyle); 87 | 88 | if (!list.global) { 89 | delete list.global; 90 | } 91 | } else { 92 | for (var link in data.links) { 93 | data.links[link] = "#" + link.replace(/#/g, "..").replace(/~/g, "--"); 94 | } 95 | 96 | list[out] = parser(layout(data), data, renderStyle); 97 | } 98 | 99 | return list; 100 | }; 101 | 102 | module.exports = { 103 | prepare: prepare, 104 | render: render 105 | }; 106 | -------------------------------------------------------------------------------- /src/flash/flashembedder.js: -------------------------------------------------------------------------------- 1 | var swfobject = require('../lib/browser/swfobject'); 2 | 3 | /** 4 | * Модуль загрузки флеш-плеера 5 | * @namespace 6 | * @private 7 | */ 8 | var FlashEmbedder = { 9 | 10 | /** 11 | * CSS-class for swf wrapper. 12 | * @protected 13 | * @default femb-swf-wrapper 14 | * @type String 15 | */ 16 | __SWF_WRAPPER_CLASS: 'femb-swf-wrapper', 17 | 18 | /** 19 | * Timeout for flash block detect 20 | * @default 500 21 | * @protected 22 | * @type Number 23 | */ 24 | __TIMEOUT: 500, 25 | 26 | /** 27 | * Embed SWF info page. This function has same options as swfobject.embedSWF 28 | * @see http://code.google.com/p/swfobject/wiki/api 29 | * @param swfUrlStr 30 | * @param replaceElemIdStr 31 | * @param widthStr 32 | * @param heightStr 33 | * @param swfVersionStr 34 | * @param xiSwfUrlStr 35 | * @param flashvarsObj 36 | * @param parObj 37 | * @param attObj 38 | * @param callbackFn 39 | */ 40 | embedSWF: function( 41 | swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, 42 | parObj, attObj, callbackFn 43 | ) { 44 | swfobject.addDomLoadEvent(function() { 45 | var replaceElement = document.getElementById(replaceElemIdStr); 46 | if (!replaceElement) { 47 | return; 48 | } 49 | 50 | // We need to create div-wrapper because some flash block plugins replace swf with another content. 51 | // Also some flash requires wrapper to work properly. 52 | var wrapper = document.createElement('div'); 53 | wrapper.className = FlashEmbedder.__SWF_WRAPPER_CLASS; 54 | 55 | replaceElement.parentNode.replaceChild(wrapper, replaceElement); 56 | wrapper.appendChild(replaceElement); 57 | 58 | swfobject.embedSWF(swfUrlStr, 59 | replaceElemIdStr, 60 | widthStr, 61 | heightStr, 62 | swfVersionStr, 63 | xiSwfUrlStr, 64 | flashvarsObj, 65 | parObj, 66 | attObj, 67 | function(e) { 68 | // e.success === false means that browser don't have flash or flash is too old 69 | // @see http://code.google.com/p/swfobject/wiki/api 70 | if (!e || e.success === false) { 71 | callbackFn(e); 72 | } else { 73 | var swfElement = e['ref']; 74 | // Opera 11.5 and above replaces flash with SVG button 75 | // msie (and canary chrome 32.0) crashes on swfElement['getSVGDocument']() 76 | var replacedBySVG = false; 77 | try { 78 | replacedBySVG = swfElement && swfElement['getSVGDocument'] 79 | && swfElement['getSVGDocument'](); 80 | } catch(err) { 81 | } 82 | if (replacedBySVG) { 83 | onFailure(e); 84 | 85 | } else { 86 | //set timeout to let FlashBlock plugin detect swf and replace it some contents 87 | window.setTimeout(function() { 88 | callbackFn(e); 89 | }, FlashEmbedder.__TIMEOUT); 90 | } 91 | } 92 | 93 | function onFailure(e) { 94 | e.success = false; 95 | callbackFn(e); 96 | } 97 | }); 98 | }); 99 | } 100 | }; 101 | 102 | module.exports = FlashEmbedder; 103 | -------------------------------------------------------------------------------- /src/lib/class/proxy.js: -------------------------------------------------------------------------------- 1 | var Events = require('../async/events'); 2 | 3 | //THINK: изучить как работает ES 2015 Proxy и попробовать использовать 4 | 5 | /** 6 | * @classdesc Прокси-класс. Выдаёт наружу лишь публичные методы объекта и статические свойства. 7 | * Не копирует методы из Object.prototype. Все методы имеют привязку контекста к проксируемому объекту. 8 | * 9 | * @param {Object} [object] - объект, который требуется проксировать 10 | * @constructor 11 | * @private 12 | */ 13 | var Proxy = function(object) { 14 | if (object) { 15 | for (var key in object) { 16 | if (key[0] === "_" 17 | || typeof object[key] !== "function" 18 | || object[key] === Object.prototype[key] 19 | || object.hasOwnProperty(key) 20 | || Events.prototype.hasOwnProperty(key)) { 21 | continue; 22 | } 23 | 24 | this[key] = object[key].bind(object); 25 | } 26 | 27 | if (object.pipeEvents) { 28 | Events.call(this); 29 | 30 | this.on = Events.prototype.on; 31 | this.once = Events.prototype.once; 32 | this.off = Events.prototype.off; 33 | this.clearListeners = Events.prototype.clearListeners; 34 | 35 | object.pipeEvents(this); 36 | } 37 | } 38 | }; 39 | 40 | /** 41 | * Экспортирует статические свойства из одного объекта в другой, исключая указанные, приватные и прототип 42 | * @param {Object} from - откуда копировать 43 | * @param {Object} to - куда копировать 44 | * @param {Array.} [exclude] - свойства которые требуется исключить 45 | */ 46 | Proxy.exportStatic = function(from, to, exclude) { 47 | exclude = exclude || []; 48 | 49 | Object.keys(from).forEach(function(key) { 50 | if (!from.hasOwnProperty(key) 51 | || key[0] === "_" 52 | || key === "prototype" 53 | || exclude.indexOf(key) !== -1) { 54 | return; 55 | } 56 | 57 | to[key] = from[key]; 58 | }); 59 | }; 60 | 61 | /** 62 | * Создание прокси-пласса привязанного к указанному классу. Можно назначить родительский класс. 63 | * У родительского класса появляется приватный метод _proxy, который выдаёт прокси-объект для 64 | * данного экземляра. Также появляется свойство __proxy, содержащее ссылку на созданный прокси-объект 65 | * 66 | * @param {function} OriginalClass - оригинальный класс 67 | * @param {function} [ParentProxyClass=Proxy] - родительский класс 68 | * @returns {function} -- конструтор проксированного класса 69 | */ 70 | Proxy.createClass = function(OriginalClass, ParentProxyClass, excludeStatic) { 71 | 72 | var ProxyClass = function() { 73 | var OriginalClassConstructor = function() {}; 74 | OriginalClassConstructor.prototype = OriginalClass.prototype; 75 | 76 | var original = new OriginalClassConstructor(); 77 | OriginalClass.apply(original, arguments); 78 | 79 | return original._proxy(); 80 | }; 81 | 82 | var ParentProxyClassConstructor = function() {}; 83 | ParentProxyClassConstructor.prototype = (ParentProxyClass || Proxy).prototype; 84 | ProxyClass.prototype = new ParentProxyClassConstructor(); 85 | 86 | var val; 87 | for (var k in OriginalClass.prototype) { 88 | val = OriginalClass.prototype[k]; 89 | if (Object.prototype[k] == val || typeof val === "function" || k[0] === "_") { 90 | continue; 91 | } 92 | ProxyClass.prototype[k] = val; 93 | } 94 | 95 | var createProxy = function(original) { 96 | var proto = Proxy.prototype; 97 | Proxy.prototype = ProxyClass.prototype; 98 | var proxy = new Proxy(original); 99 | Proxy.prototype = proto; 100 | return proxy; 101 | }; 102 | 103 | OriginalClass.prototype._proxy = function() { 104 | if (!this.__proxy) { 105 | this.__proxy = createProxy(this); 106 | } 107 | 108 | return this.__proxy; 109 | }; 110 | 111 | if (!excludeStatic) { 112 | Proxy.exportStatic(OriginalClass, ProxyClass); 113 | } 114 | 115 | return ProxyClass; 116 | }; 117 | 118 | module.exports = Proxy; 119 | -------------------------------------------------------------------------------- /spec/Equalizer.md: -------------------------------------------------------------------------------- 1 | ## *class* Equalizer 2 | 3 | **Доступен извне как:** `ya.music.Audio.fx.Equalizer` 4 | 5 | Эквалайзер. 6 | 7 | **Расширяет:** 8 | 9 | - [Events](Events.md#Events) 10 | 11 | **Триггерит:** 12 | 13 | - [Equalizer.EVENT_CHANGE](Equalizer.md#Equalizer.EVENT_CHANGE) 14 | 15 | #### new Equalizer(audioContext: AudioContext, bands: Array.< Number >) 16 | 17 | | Имя | Тип | * | Описание | 18 | | --- | --- | --- | --- | 19 | | audioContext | AudioContext | | Контекст Web Audio API. | 20 | | bands | Array.< Number > | | Список частот для полос эквалайзера. | 21 | 22 | #### Equalizer.DEFAULT_BANDS : Array.< Number > 23 | 24 | Набор частот эквалайзера, применяющийся по умолчанию. 25 | 26 | #### Equalizer.DEFAULT_PRESETS : Object.< String, [EqualizerPreset](Equalizer.md#EqualizerPreset) > 27 | 28 | Набор распространенных пресетов эквалайзера для набора частот по умолчанию. 29 | 30 | ---- 31 | 32 | ### События 33 | 34 | #### *event* Equalizer.EVENT_CHANGE 35 | 36 | Событие изменения полосы пропускания 37 | 38 | | Имя | Тип | * | Описание | 39 | | --- | --- | --- | --- | 40 | | freq | Number | | Частота полосы пропускания. | 41 | | value | Number | | Значение усиления. | 42 | 43 | ---- 44 | 45 | ### Методы 46 | 47 | #### Equalizer#loadPreset (preset: [EqualizerPreset](Equalizer.md#EqualizerPreset)) 48 | 49 | Загрузить настройки. 50 | 51 | | Имя | Тип | * | Описание | 52 | | --- | --- | --- | --- | 53 | | preset | [EqualizerPreset](Equalizer.md#EqualizerPreset) | | Настройки. | 54 | 55 | #### Equalizer#savePreset () : [EqualizerPreset](Equalizer.md#EqualizerPreset) 56 | 57 | Сохранить текущие настройки. 58 | 59 | #### Equalizer#guessPreamp () : number 60 | 61 | Вычисляет оптимальное значение предусиления. Функция является экспериментальной. 62 | 63 | > **Возвращает:** значение предусиления. 64 | 65 | #### Equalizer#on (event: String, callback: function) : [Events](Events.md#Events) *(inherits [Events#on](Events.md#Events..on))* 66 | 67 | Подписаться на событие (цепочный метод). 68 | 69 | | Имя | Тип | * | Описание | 70 | | --- | --- | --- | --- | 71 | | event | String | | Имя события. | 72 | | callback | function | | Обработчик события. | 73 | 74 | > **Возвращает:** ссылку на контекст. 75 | 76 | #### Equalizer#off (event: String, callback: function) : [Events](Events.md#Events) *(inherits [Events#off](Events.md#Events..off))* 77 | 78 | Отписаться от события (цепочный метод). 79 | 80 | | Имя | Тип | * | Описание | 81 | | --- | --- | --- | --- | 82 | | event | String | | Имя события. | 83 | | callback | function | | Обработчик события. | 84 | 85 | > **Возвращает:** ссылку на контекст. 86 | 87 | #### Equalizer#once (event: String, callback: function) : [Events](Events.md#Events) *(inherits [Events#once](Events.md#Events..once))* 88 | 89 | Подписаться на событие и отписаться сразу после его первого возникновения (цепочный метод). 90 | 91 | | Имя | Тип | * | Описание | 92 | | --- | --- | --- | --- | 93 | | event | String | | Имя события. | 94 | | callback | function | | Обработчик события. | 95 | 96 | > **Возвращает:** ссылку на контекст. 97 | 98 | #### Equalizer#clearListeners () : [Events](Events.md#Events) *(inherits [Events#clearListeners](Events.md#Events..clearListeners))* 99 | 100 | Отписаться от всех слушателей событий (цепочный метод). 101 | 102 | > **Возвращает:** ссылку на контекст. 103 | 104 | #### Equalizer#muteEvents () : [Events](Events.md#Events) *(inherits [Events#muteEvents](Events.md#Events..muteEvents))* 105 | 106 | Остановить запуск событий (цепочный метод). 107 | 108 | > **Возвращает:** ссылку на контекст. 109 | 110 | #### Equalizer#unmuteEvents () : [Events](Events.md#Events) *(inherits [Events#unmuteEvents](Events.md#Events..unmuteEvents))* 111 | 112 | Возобновить запуск событий (цепочный метод). 113 | 114 | > **Возвращает:** ссылку на контекст. 115 | 116 | ---- 117 | 118 | ### Типы 119 | 120 | #### *type* EqualizerPreset : Object 121 | 122 | Описание настроек эквалайзера. 123 | 124 | | Имя | Тип | * | Описание | 125 | | --- | --- | --- | --- | 126 | | *\[id\]* | String | | Идентификатор настроек. | 127 | | preamp | Number | | Предусилитель. | 128 | | bands | Array.< Number > | | Значения для полос эквалайзера. | 129 | 130 | -------------------------------------------------------------------------------- /src/flash/src/AudioManager.as: -------------------------------------------------------------------------------- 1 | package { 2 | import flash.display.Sprite; 3 | import flash.display.StageAlign; 4 | import flash.display.StageScaleMode; 5 | import flash.events.MouseEvent; 6 | import flash.external.ExternalInterface; 7 | import flash.media.SoundMixer; 8 | import flash.media.SoundTransform; 9 | import flash.system.Security; 10 | 11 | [SWF(width="10", height="10")] 12 | public final class AudioManager extends Sprite { 13 | private static var players:Array/*AudioFlash*/ = []; 14 | private static var volume:Number = 1; 15 | 16 | public static const EVENT_INIT:String = "init"; 17 | public static const EVENT_CLICK:String = "click"; 18 | public static const EVENT_FAIL:String = "failed"; 19 | 20 | private static const FLASH2JS_CALLBACK:String = "Audio._flashCallback"; 21 | private static const JS2FLASH_CALLBACK:String = "call"; 22 | 23 | public function AudioManager() { 24 | Security.allowDomain("*"); 25 | 26 | try { 27 | ExternalInterface.addCallback(JS2FLASH_CALLBACK, this[JS2FLASH_CALLBACK]); 28 | } catch (error:Error) { 29 | jsCall(EVENT_FAIL, -1, -1, error.message); 30 | } 31 | 32 | this.addEventListener(MouseEvent.CLICK, onClick); 33 | 34 | stage.scaleMode = StageScaleMode.EXACT_FIT; 35 | stage.align = StageAlign.TOP_LEFT; 36 | 37 | var overlay:Sprite = new Sprite(); 38 | overlay.x = 0; 39 | overlay.y = 0; 40 | overlay.useHandCursor = true; 41 | overlay.buttonMode = true; 42 | overlay.graphics.beginFill(0, 0); 43 | overlay.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight); 44 | overlay.graphics.endFill(); 45 | this.addChild(overlay); 46 | 47 | jsCall(EVENT_INIT); 48 | } 49 | 50 | private static function onClick(event:MouseEvent):void { 51 | jsCall(EVENT_CLICK); 52 | } 53 | 54 | private static function jsCall(eventName:String, id:int = -1, offset:int = -1, error:String = ""):void { 55 | try { 56 | ExternalInterface.call(FLASH2JS_CALLBACK, eventName, id, offset, error); 57 | } catch (error:Error) { 58 | trace(error.message); 59 | } 60 | } 61 | 62 | private static function delegateEvent(event:AudioEvent):void { 63 | var player:int = players.indexOf(event.currentTarget); 64 | jsCall(event.type, player, event.offset, event.data); 65 | } 66 | 67 | public function call(methodName:String, id:int = -1, ...args):* { 68 | try { 69 | if (id !== -1) { 70 | var player:AudioPlayer = players[id]; 71 | if (!player) { 72 | jsCall(AudioEvent.EVENT_ERROR, id, -1, "no such player"); 73 | return null; 74 | } 75 | 76 | if (typeof player[methodName] === "function") { 77 | return player[methodName].apply(player, args); 78 | } else { 79 | jsCall(AudioEvent.EVENT_ERROR, id, -1, "no such method"); 80 | return null; 81 | } 82 | } else { 83 | if (typeof this["export_" + methodName] === "function") { 84 | return this["export_" + methodName].apply(this, args); 85 | } else { 86 | jsCall(AudioEvent.EVENT_ERROR, id, -1, "no such method"); 87 | return null; 88 | } 89 | } 90 | } catch (e:Error) { 91 | jsCall(AudioEvent.EVENT_ERROR, id, args[0], e.message); 92 | return null; 93 | } 94 | } 95 | 96 | private function export_addPlayer():uint { 97 | var player:AudioPlayer = new AudioPlayer(players.length); 98 | 99 | player.addEventListener(AudioEvent.EVENT_PLAY, delegateEvent); 100 | player.addEventListener(AudioEvent.EVENT_PAUSE, delegateEvent); 101 | player.addEventListener(AudioEvent.EVENT_ENDED, delegateEvent); 102 | player.addEventListener(AudioEvent.EVENT_PROGRESS, delegateEvent); 103 | player.addEventListener(AudioEvent.EVENT_ERROR, delegateEvent); 104 | player.addEventListener(AudioEvent.EVENT_STOP, delegateEvent); 105 | player.addEventListener(AudioEvent.EVENT_LOADED, delegateEvent); 106 | player.addEventListener(AudioEvent.EVENT_LOADING, delegateEvent); 107 | 108 | player.addEventListener(AudioEvent.EVENT_DEBUG, delegateEvent); 109 | 110 | players.push(player); 111 | return player.id; 112 | } 113 | 114 | private function export_setVolume(value:Number):void { 115 | value = Math.max(0, Math.min(1, value)); 116 | if (0 < value && value < 0.01) { 117 | value = 0.01; 118 | } 119 | volume = value; 120 | SoundMixer.soundTransform = new SoundTransform(value, 0); 121 | jsCall(AudioEvent.EVENT_VOLUME); 122 | } 123 | 124 | private function export_getVolume():Number { 125 | return volume; 126 | } 127 | 128 | private function export_heartBeat():Boolean { 129 | return true; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/fx/equalizer/equalizer.js: -------------------------------------------------------------------------------- 1 | var Events = require('../../lib/async/events'); 2 | var merge = require('../../lib/data/merge'); 3 | 4 | var EqualizerStatic = require('./equalizer-static'); 5 | var EqualizerBand = require('./equalizer-band'); 6 | 7 | /** 8 | * Описание настроек эквалайзера. 9 | * @typedef {Object} Equalizer~EqualizerPreset 10 | * @property {String} [id] Идентификатор настроек. 11 | * @property {Number} preamp Предусилитель. 12 | * @property {Array.} bands Значения для полос эквалайзера. 13 | */ 14 | 15 | /** 16 | * Событие изменения полосы пропускания. 17 | * @event Equalizer.EVENT_CHANGE 18 | * 19 | * @param {Number} freq Частота полосы пропускания. 20 | * @param {Number} value Значение усиления. 21 | */ 22 | 23 | // ================================================================= 24 | 25 | // Конструктор 26 | 27 | // ================================================================= 28 | 29 | /** 30 | * @classdesc Эквалайзер. 31 | * @exported ya.music.Audio.fx.Equalizer 32 | * 33 | * @param {AudioContext} audioContext Контекст Web Audio API. 34 | * @param {Array.} bands Список частот для полос эквалайзера. 35 | * 36 | * @extends Events 37 | * 38 | * @fires Equalizer.EVENT_CHANGE 39 | * 40 | * @constructor 41 | */ 42 | var Equalizer = function(audioContext, bands) { 43 | Events.call(this); 44 | 45 | this.preamp = new EqualizerBand(audioContext, "highshelf", 0); 46 | this.preamp.on("*", this._onBandEvent.bind(this, this.preamp)); 47 | 48 | bands = bands || Equalizer.DEFAULT_BANDS; 49 | 50 | var prev; 51 | this.bands = bands.map(function(frequency, idx) { 52 | var band = new EqualizerBand( 53 | audioContext, 54 | 55 | idx == 0 ? 'lowshelf' 56 | : idx + 1 < bands.length ? "peaking" 57 | : "highshelf", 58 | 59 | frequency 60 | ); 61 | band.on("*", this._onBandEvent.bind(this, band)); 62 | 63 | if (!prev) { 64 | this.preamp.filter.connect(band.filter); 65 | } else { 66 | prev.filter.connect(band.filter); 67 | } 68 | 69 | prev = band; 70 | return band; 71 | }.bind(this)); 72 | 73 | this.input = this.preamp.filter; 74 | this.output = this.bands[this.bands.length - 1].filter; 75 | }; 76 | Events.mixin(Equalizer); 77 | merge(Equalizer, EqualizerStatic, true); 78 | 79 | // ================================================================= 80 | 81 | // Настройки по-умолчанию 82 | 83 | // ================================================================= 84 | 85 | /** 86 | * Набор частот эквалайзера, применяющийся по умолчанию. 87 | * @type {Array.} 88 | * @const 89 | */ 90 | Equalizer.DEFAULT_BANDS = require('./default.bands.js'); 91 | 92 | /** 93 | * Набор распространенных пресетов эквалайзера для набора частот по умолчанию. 94 | * @type {Object.} 95 | * @const 96 | */ 97 | Equalizer.DEFAULT_PRESETS = require('./default.presets.js'); 98 | 99 | // ================================================================= 100 | 101 | // Обработка событий 102 | 103 | // ================================================================= 104 | 105 | /** 106 | * Обработка события полосы эквалайзера. 107 | * @param {EqualizerBand} band - полоса эквалайзера 108 | * @param {String} event - событие 109 | * @param {*} data - данные события 110 | * @private 111 | */ 112 | Equalizer.prototype._onBandEvent = function(band, event, data) { 113 | this.trigger(event, band.getFreq(), data); 114 | }; 115 | 116 | // ================================================================= 117 | 118 | // Загрузка и сохранение настроек 119 | 120 | // ================================================================= 121 | 122 | /** 123 | * Загрузить настройки. 124 | * @param {Equalizer~EqualizerPreset} preset Настройки. 125 | */ 126 | Equalizer.prototype.loadPreset = function(preset) { 127 | preset.bands.forEach(function(value, idx) { 128 | this.bands[idx].setValue(value); 129 | }.bind(this)); 130 | this.preamp.setValue(preset.preamp); 131 | }; 132 | 133 | /** 134 | * Сохранить текущие настройки. 135 | * @returns {Equalizer~EqualizerPreset} 136 | */ 137 | Equalizer.prototype.savePreset = function() { 138 | return { 139 | preamp: this.preamp.getValue(), 140 | bands: this.bands.map(function(band) { return band.getValue(); }) 141 | }; 142 | }; 143 | 144 | // ================================================================= 145 | 146 | // Математика 147 | 148 | // ================================================================= 149 | 150 | //TODO: проверить предположение (скорее всего нужна карта весов для различных частот или даже некая функция) 151 | /** 152 | * Вычисляет оптимальное значение предусиления. Функция является экспериментальной. 153 | * @experimental 154 | * @returns {number} значение предусиления. 155 | */ 156 | Equalizer.prototype.guessPreamp = function() { 157 | var v = 0; 158 | for (var k = 0, l = this.bands.length; k < l; k++) { 159 | v += this.bands[k].getValue(); 160 | } 161 | 162 | return -v / 2; 163 | }; 164 | 165 | module.exports = Equalizer; 166 | -------------------------------------------------------------------------------- /test/web-audio-api/index.js: -------------------------------------------------------------------------------- 1 | var template = document.querySelector(".template").innerHTML; 2 | document.body.removeChild(document.querySelector(".template")); 3 | 4 | var WIDTH = 250; 5 | var HEIGHT = 150; 6 | var GUTTER = 2; 7 | 8 | var INNER_HEIGHT = HEIGHT - GUTTER * 2; 9 | var INNER_WIDTH = WIDTH - GUTTER * 2; 10 | 11 | var MIN_FREQ = 20; 12 | var MAX_FREQ = 20000; 13 | var FREQ_STEPS = INNER_WIDTH + 1; 14 | 15 | var A = Math.exp((Math.log(MAX_FREQ) - Math.log(MIN_FREQ)) / (FREQ_STEPS - 1)); 16 | var B = Math.log(MIN_FREQ) / Math.log(A); 17 | 18 | var FILTER_LINE = 0.5; 19 | var MIN_DB = -2; 20 | var MAX_DB = 2; 21 | 22 | var norm = function(points, min, max) { 23 | var k = INNER_HEIGHT / (max - min) || 1; 24 | var dy = min * k; 25 | var step = INNER_WIDTH / (points.length - 1); 26 | 27 | return points 28 | .map(function(v) { return INNER_HEIGHT - ((v || 0) * k - dy); }) 29 | .map(function(v, idx) { return (GUTTER + idx * step) + " " + (GUTTER + (v || 0)); }); 30 | }; 31 | 32 | var drawTest = function(name, data) { 33 | var testBlock = document.createElement("DIV"); 34 | testBlock.classList.add("test"); 35 | 36 | var id = name.replace(/[ =.]/g, "_"); 37 | 38 | testBlock.innerHTML = template 39 | .replace("{{title}}", name) 40 | .replace("{{name}}", id); 41 | document.body.appendChild(testBlock); 42 | 43 | var canvas = Raphael(id, WIDTH, HEIGHT); 44 | 45 | var h = "0 " + (GUTTER + INNER_HEIGHT / 2); 46 | var v = (GUTTER + FILTER_LINE * INNER_WIDTH) + " 0"; 47 | 48 | canvas.path().attr({ 49 | path: "M" + v + "L" + v + " l0 " + HEIGHT, 50 | stroke: "#ff0000", 51 | "stroke-width": 1, 52 | "stroke-linejoin": "round" 53 | }); 54 | 55 | canvas.path().attr({ 56 | path: "M" + h + "L" + h + " l" + WIDTH + " 0", 57 | stroke: "#000", 58 | "stroke-width": 1, 59 | "stroke-linejoin": "round" 60 | }); 61 | 62 | var draw = function(path, color, width) { 63 | canvas.path().attr({ 64 | path: "M" + path[0] + " L" + path.join(" "), 65 | stroke: color, 66 | "stroke-width": 1 + width, 67 | "stroke-linejoin": "round" 68 | }); 69 | }; 70 | 71 | draw(norm(data[1], -Math.PI, Math.PI), "#5B9A1C", 3); 72 | draw(norm(data[0], 0, MAX_DB), "#134b9a", 3); 73 | }; 74 | 75 | var audioCtx = new AudioContext(); 76 | var freqs = new Float32Array(FREQ_STEPS); 77 | 78 | for (var k = 0; k < FREQ_STEPS; k++) { 79 | freqs[k] = Math.pow(A, B + k); 80 | } 81 | 82 | var FILTER_FREQ = freqs[Math.floor(FREQ_STEPS * FILTER_LINE)]; 83 | console.log(FILTER_FREQ); 84 | 85 | var testFilter = function(options, phase) { 86 | return function() { 87 | var filter = audioCtx.createBiquadFilter(); 88 | for (var key in options) { 89 | if (options.hasOwnProperty(key)) { 90 | if (key !== "type") { 91 | filter[key].value = options[key]; 92 | } else { 93 | filter[key] = options[key]; 94 | } 95 | } 96 | } 97 | 98 | var resFreq = new Float32Array(FREQ_STEPS); 99 | var resPhase = new Float32Array(FREQ_STEPS); 100 | 101 | filter.frequency.value = FILTER_FREQ; 102 | 103 | filter.getFrequencyResponse(freqs, resFreq, resPhase); 104 | 105 | resFreq.map = resPhase.map = Array.prototype.map; 106 | return [resFreq, resPhase]; 107 | } 108 | }; 109 | 110 | var tests = { 111 | "lowpass Q=0.2": testFilter({type: "lowpass", Q: 0.2}), 112 | "lowpass Q=1": testFilter({type: "lowpass", Q: 1}), 113 | "lowpass Q=5": testFilter({type: "lowpass", Q: 5}), 114 | 115 | "highpass Q=0.2": testFilter({type: "highpass", Q: 0.2}), 116 | "highpass Q=1": testFilter({type: "highpass", Q: 1}), 117 | "highpass Q=5": testFilter({type: "highpass", Q: 5}), 118 | 119 | "bandpass Q=0.2": testFilter({type: "bandpass", Q: 0.2}), 120 | "bandpass Q=1": testFilter({type: "bandpass", Q: 1}), 121 | "bandpass Q=5": testFilter({type: "bandpass", Q: 5}), 122 | 123 | "notch Q=0.2": testFilter({type: "notch", Q: 0.2}), 124 | "notch Q=1": testFilter({type: "notch", Q: 1}), 125 | "notch Q=5": testFilter({type: "notch", Q: 5}), 126 | 127 | "peaking gain=6 Q=0.2": testFilter({type: "peaking", Q: 0.2, gain: 6}), 128 | "peaking gain=6 Q=1": testFilter({type: "peaking", Q: 1, gain: 6}), 129 | "peaking gain=6 Q=5": testFilter({type: "peaking", Q: 5, gain: 6}), 130 | 131 | "peaking gain=-6 Q=0.2": testFilter({type: "peaking", Q: 0.2, gain: -6}), 132 | "peaking gain=-6 Q=1": testFilter({type: "peaking", Q: 1, gain: -6}), 133 | "peaking gain=-6 Q=5": testFilter({type: "peaking", Q: 5, gain: -6}), 134 | 135 | "allpass Q=0.2": testFilter({type: "allpass", Q: 0.2}), 136 | "allpass Q=1": testFilter({type: "allpass", Q: 1}), 137 | "allpass Q=5": testFilter({type: "allpass", Q: 5}), 138 | 139 | "lowshelf gain=6": testFilter({type: "lowshelf", gain: 6}), 140 | "lowshelf gain=0": testFilter({type: "lowshelf", gain: 0}), 141 | "lowshelf gain=-6": testFilter({type: "lowshelf", gain: -6}), 142 | 143 | "highshelf gain=6": testFilter({type: "highshelf", gain: 6}), 144 | "highshelf gain=0": testFilter({type: "highshelf", gain: 0}), 145 | "highshelf gain=-6": testFilter({type: "highshelf", gain: -6}) 146 | }; 147 | 148 | Object.keys(tests).forEach(function(testname) { 149 | drawTest(testname, tests[testname]()); 150 | }); 151 | -------------------------------------------------------------------------------- /src/lib/browser/detect.js: -------------------------------------------------------------------------------- 1 | var ua = navigator.userAgent.toLowerCase(); 2 | 3 | // ================================================================= 4 | 5 | // Получение данных о браузере 6 | 7 | // ================================================================= 8 | 9 | // Useragent RegExp 10 | var ruc = /(ucbrowser)\/([\w.]+)/; 11 | var rwebkit = /(webkit)[ \/]([\w.]+)/; 12 | var ryabro = /(yabrowser)[ \/]([\w.]+)/; 13 | var ropera = /(opr|opera)(?:.*version)?[ \/]([\w.]+)/; 14 | var rmsie = /(msie) ([\w.]+)/; 15 | var redge = /(edge)\/([\w.]+)/; 16 | var rmmsie = /(iemobile)\/([\d\.]+)/; 17 | var rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/; 18 | var rsafari = /^((?!chrome).)*version\/([\d\w\.]+).*(safari)/; 19 | 20 | // Порядок очень важен: 21 | // у Ya, Opera и Edge есть и chrome и safari, но не факт, что одновременно, 22 | // поэтому чекаем их первыми, что бы избежать false positive 23 | // у хрома есть сафари, но у сафари нет хрома, поэтому сафари идет первым 24 | // хром считаем неким абстрактным вебкит-браузером, точнее никак, много притворяющихся 25 | var match = ruc.exec(ua) 26 | || ryabro.exec(ua) 27 | || ropera.exec(ua) 28 | || redge.exec(ua) 29 | || rsafari.exec(ua) 30 | || rmmsie.exec(ua) 31 | || rwebkit.exec(ua) 32 | || rmsie.exec(ua) 33 | || ua.indexOf("compatible") < 0 && rmozilla.exec(ua) 34 | || []; 35 | 36 | var browser = {name: match[1] || "", version: match[2] || "0"}; 37 | 38 | if (match[3] === "safari") { 39 | browser.name = match[3]; 40 | // Сафари четче различать по версии вебкита, а не по маркетинговой 41 | browser.version = rwebkit.exec(ua)[2] 42 | } 43 | 44 | if (browser.name === 'msie') { 45 | if (document.documentMode) { // IE8 or later 46 | browser.documentMode = document.documentMode; 47 | } else { // IE 5-7 48 | browser.documentMode = 5; // Assume quirks mode unless proven otherwise 49 | if (document.compatMode) { 50 | if (document.compatMode === "CSS1Compat") { 51 | browser.documentMode = 7; // standards mode 52 | } 53 | } 54 | } 55 | } 56 | 57 | if (browser.name === "opr") { 58 | browser.name = "opera"; 59 | } 60 | 61 | //INFO: IE (как всегда) не корректно выставляет user-agent 62 | if (browser.name === "mozilla" && browser.version.split(".")[0] === "11") { 63 | browser.name = "msie"; 64 | } 65 | 66 | // ================================================================= 67 | 68 | // Получение данных о платформе 69 | 70 | // ================================================================= 71 | 72 | // Useragent RegExp 73 | var rplatform = /(windows phone|ipad|iphone|ipod|android|blackberry|playbook|windows ce)/; 74 | var rtablet = /(ipad|playbook)/; 75 | var randroid = /(android)/; 76 | var rmobile = /(mobile)/; 77 | var rtv = /(netcast|web[0o]s|nettv|netrange|sharp|smart-tv)/; 78 | 79 | platform = rplatform.exec(ua) || []; 80 | var tablet = rtablet.exec(ua) || !rmobile.exec(ua) && randroid.exec(ua) || []; 81 | var tv = rtv.exec(ua) || (!!window.tizen ? [null, 'tizen'] : false) || (typeof window.sony == 'object' && window.sony.tv ? [null, 'sony'] : false) || []; 82 | 83 | if (platform[1]) { 84 | platform[1] = platform[1].replace(/\s/g, "_"); // Change whitespace to underscore. Enables dot notation. 85 | } 86 | 87 | if (tv[1] == 'web0s') { 88 | tv[1] = 'webos'; 89 | } 90 | 91 | var platform = { 92 | type: platform[1] || "", 93 | tablet: !!tablet[1], 94 | tv: !!tv[1], 95 | mobile: platform[1] && !tablet[1] || false 96 | }; 97 | if (!!tv[1]) { 98 | platform.type = tv[1]; 99 | } 100 | if (!platform.type) { 101 | platform.type = 'pc'; 102 | } 103 | 104 | platform.os = platform.type; 105 | if (platform.type === 'ipad' || platform.type === 'iphone' || platform.type === 'ipod') { 106 | platform.os = 'ios'; 107 | } else if (platform.type === 'android') { 108 | platform.os = 'android'; 109 | } else if (platform.type === "windows phone" || navigator.appVersion.indexOf("Win") !== -1) { 110 | platform.os = "windows"; 111 | platform.version = navigator.userAgent.match(/win[^ ]* ([^;]*)/i); 112 | platform.version = platform.version && platform.version[1]; 113 | } else if (navigator.appVersion.indexOf("Mac") !== -1) { 114 | platform.os = "macos"; 115 | } else if (navigator.appVersion.indexOf("X11") !== -1) { 116 | platform.os = "unix"; 117 | } else if (navigator.appVersion.indexOf("Linux") !== -1) { 118 | platform.os = "linux"; 119 | } 120 | 121 | // ================================================================= 122 | 123 | // Получение данных о возможности менять громкость 124 | 125 | // ================================================================= 126 | var noVolume = true; 127 | try { 128 | var audio = document.createElement('audio'); 129 | audio.volume = 0.63; 130 | noVolume = Math.abs(audio.volume - 0.63) > 0.01; 131 | } catch(e) { 132 | noVolume = true; 133 | } 134 | 135 | /** 136 | * Информация об окружении. 137 | * @namespace 138 | * @exported ya.music.info 139 | */ 140 | var info = { 141 | /** 142 | * Информация о браузере. 143 | * @namespace 144 | * @property {string} name Название браузера. 145 | * @property {string} version Версия. 146 | * @property {number} [documentMode] Версия документа (для IE). 147 | */ 148 | browser: browser, 149 | 150 | /** 151 | * Информация о платформе. 152 | * @namespace 153 | * @property {string} os Тип операционной системы. 154 | * @property {string} type Тип платформы. 155 | * @property {Boolean} tablet Планшет. 156 | * @property {Boolean} mobile Мобильный. 157 | * @property {boolean} tv Телевизор 158 | */ 159 | platform: platform, 160 | 161 | /** 162 | * Настройка громкости. 163 | * @type {Boolean} 164 | */ 165 | onlyDeviceVolume: noVolume 166 | }; 167 | 168 | module.exports = info; 169 | -------------------------------------------------------------------------------- /examples/quick-start/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Теперь инициализируем всю эту структуру и создадим экземпляр плеера. 4 | var AudioPlayer = ya.music.Audio; 5 | AudioPlayer.config.flash.path = "../../dist"; 6 | 7 | var dom = { 8 | player: document.querySelector(".player"), 9 | 10 | play: document.querySelector(".controls_play"), 11 | 12 | progress: { 13 | bar: document.querySelector(".progress"), 14 | loaded: document.querySelector(".progress_loaded"), 15 | current: document.querySelector(".progress_current") 16 | }, 17 | 18 | volume: { 19 | bar: document.querySelector(".volume"), 20 | value: document.querySelector(".volume_bar") 21 | }, 22 | 23 | overlay: document.querySelector(".overlay") 24 | }; 25 | 26 | // Предоставим плееру самому решать, какой тип реализации использовать. 27 | var audioPlayer = new AudioPlayer(null, dom.overlay); 28 | 29 | audioPlayer.initPromise().then(function() { 30 | // Скрываем оверлей, кнопки управления становятся доступными. 31 | dom.overlay.classList.add("overlay_hidden"); 32 | }, function(err) { 33 | // Показываем ошибку инициализации в оверлее. 34 | dom.overlay.innerHTML = err.message; 35 | dom.overlay.classList.add("overlay_error"); 36 | }); 37 | 38 | // Настроим отображение статуса плеера. 39 | // Для простого плеера нам достаточно знать, запущено воспроизведение или нет. 40 | 41 | audioPlayer.on(ya.music.Audio.EVENT_STATE, function(state) { 42 | if (state === ya.music.Audio.STATE_PLAYING) { 43 | dom.player.classList.add("player_playing"); 44 | } else { 45 | dom.player.classList.remove("player_playing"); 46 | } 47 | }); 48 | 49 | /* Теперь настроим обновление прогресс-бара. В нем предусмотрены 2 шкалы - шкала загрузки и шкала текущей 50 | позиции воспроизведения. */ 51 | 52 | audioPlayer.on(ya.music.Audio.EVENT_PROGRESS, function(timings) { 53 | dom.progress.loaded.style.width = (timings.loaded / timings.duration * 100).toFixed(2) + "%"; 54 | dom.progress.current.style.width = (timings.position / timings.duration * 100).toFixed(2) + "%"; 55 | }); 56 | 57 | /* Аналогично будет работать шкала громкости */ 58 | 59 | var updateVolume = function(volume) { 60 | dom.volume.value.style.height = (volume * 100).toFixed(2) + "%"; 61 | }; 62 | audioPlayer.on(ya.music.Audio.EVENT_VOLUME, updateVolume); 63 | 64 | // Отображаем начальную громкость 65 | audioPlayer.initPromise().then(function() { 66 | updateVolume(audioPlayer.getVolume()); 67 | }); 68 | 69 | /* Теперь нужно настроить взаимодействие с пользователем. Начнем с запуска воспроизведения. */ 70 | 71 | var trackUrls = [ 72 | "https://download.cdn.yandex.net/tech/ru/audio/doc/examples/files/audio_src/Tchaikovsky1.mp3", 73 | "https://download.cdn.yandex.net/tech/ru/audio/doc/examples/files/audio_src/Tchaikovsky2.mp3", 74 | "https://download.cdn.yandex.net/tech/ru/audio/doc/examples/files/audio_src/Tchaikovsky3.mp3" 75 | ]; 76 | 77 | var trackIndex = 0; 78 | 79 | var startPlay = function() { 80 | var track = trackUrls[trackIndex]; 81 | if (audioPlayer.isPreloaded(track)) { 82 | audioPlayer.playPreloaded(track); 83 | } else { 84 | audioPlayer.play(track); 85 | } 86 | }; 87 | 88 | dom.play.addEventListener("click", function() { 89 | var state = audioPlayer.getState(); 90 | 91 | switch (state) { 92 | case ya.music.Audio.STATE_PLAYING: 93 | audioPlayer.pause(); 94 | break; 95 | 96 | case ya.music.Audio.STATE_PAUSED: 97 | audioPlayer.resume(); 98 | break; 99 | 100 | default: 101 | startPlay(); 102 | break; 103 | } 104 | }); 105 | 106 | /* Добавим немножко удобства для пользователей: сделаем автозагрузку следующего трека после того, как текущий загрузился. 107 | Для этого потребуется немного изменить функцию `startPlay` и отслеживать момент загрузки трека. */ 108 | 109 | audioPlayer.on(ya.music.Audio.EVENT_ENDED, function() { 110 | trackIndex++; 111 | 112 | if (trackIndex < trackUrls.length) { 113 | startPlay(); 114 | } 115 | }); 116 | 117 | audioPlayer.on(ya.music.Audio.EVENT_LOADED, function() { 118 | if (trackIndex + 1 < trackUrls.length) { 119 | audioPlayer.preload(trackUrls[trackIndex + 1]); 120 | } 121 | }); 122 | 123 | /* Осталось только настроить навигацию по треку и регулирование громкости: */ 124 | 125 | var offsetLeft = function(node) { 126 | var offset = node.offsetLeft; 127 | if (node.offsetParent) { 128 | offset += offsetLeft(node.offsetParent); 129 | } 130 | return offset; 131 | }; 132 | 133 | var offsetTop = function(node) { 134 | var offset = node.offsetTop; 135 | if (node.offsetParent) { 136 | offset += offsetTop(node.offsetParent); 137 | } 138 | return offset; 139 | }; 140 | 141 | dom.progress.bar.addEventListener("click", function(evt) { 142 | var fullWidth = dom.progress.bar.offsetWidth; 143 | var offset = offsetLeft(dom.progress.bar); 144 | 145 | var relativePosition = Math.max(0, Math.min(1, ((evt.pageX || evt.screenX) - offset) / fullWidth)); 146 | var duration = audioPlayer.getDuration(); 147 | 148 | audioPlayer.setPosition(duration * relativePosition); 149 | }); 150 | 151 | dom.volume.bar.addEventListener("click", function(evt) { 152 | var fullHeight = dom.volume.bar.offsetHeight; 153 | var offset = offsetTop(dom.volume.bar); 154 | 155 | // тут мы делаем "1 -" т.к. громость принято отмерять снизу, а не сверху 156 | var volume = 1 - Math.max(0, Math.min(1, ((evt.pageY || evt.screenY) - offset) / fullHeight)); 157 | audioPlayer.setVolume(volume); 158 | }); 159 | })(); 160 | -------------------------------------------------------------------------------- /src/logger/logger.js: -------------------------------------------------------------------------------- 1 | var LEVELS = ["debug", "log", "info", "warn", "error", "trace"]; 2 | var noop = require('../lib/noop'); 3 | 4 | // ================================================================= 5 | 6 | // Конструктор 7 | 8 | // ================================================================= 9 | 10 | /** 11 | * @exported ya.music.Logger 12 | * @classdesc Настраиваемый логгер для аудиоплеера. 13 | * @param {String} channel Имя канала, за который будет отвечать экземляр логгера. 14 | * @constructor 15 | */ 16 | var Logger = function(channel) { 17 | this.channel = channel; 18 | }; 19 | 20 | // ================================================================= 21 | 22 | // Настройки 23 | 24 | // ================================================================= 25 | 26 | /** 27 | * Список игнорируемых каналов. 28 | * @type {Array.} 29 | */ 30 | Logger.ignores = []; 31 | 32 | /** 33 | * Список отображаемых в консоли уровней лога. 34 | * @type {Array.} 35 | */ 36 | Logger.logLevels = []; 37 | 38 | // ================================================================= 39 | 40 | // Синтаксический сахар 41 | 42 | // ================================================================= 43 | 44 | /** 45 | * Запись в лог с уровнем **debug**. 46 | * @param {Object} context Контекст вызова. 47 | * @param {...*} [args] Дополнительные аргументы. 48 | */ 49 | Logger.prototype.debug = noop; 50 | 51 | /** 52 | * Запись в лог с уровнем **log**. 53 | * @param {Object} context Контекст вызова. 54 | * @param {...*} [args] Дополнительные аргументы. 55 | */ 56 | Logger.prototype.log = noop; 57 | 58 | /** 59 | * Запись в лог с уровнем **info**. 60 | * @param {Object} context Контекст вызова. 61 | * @param {...*} [args] Дополнительные аргументы. 62 | */ 63 | Logger.prototype.info = noop; 64 | 65 | /** 66 | * Запись в лог с уровнем **warn**. 67 | * @param {Object} context Контекст вызова. 68 | * @param {...*} [args] Дополнительные аргументы. 69 | */ 70 | Logger.prototype.warn = noop; 71 | 72 | /** 73 | * Запись в лог с уровнем **error**. 74 | * @param {Object} context Контекст вызова. 75 | * @param {...*} [args] Дополнительные аргументы. 76 | */ 77 | Logger.prototype.error = noop; 78 | 79 | /** 80 | * Запись в лог с уровнем **trace**. 81 | * @param {Object} context Контекст вызова. 82 | * @param {...*} [args] Дополнительные аргументы. 83 | */ 84 | Logger.prototype.trace = noop; 85 | 86 | /** 87 | * Метод для обработки ссылок, передаваемых в лог. 88 | * @param url 89 | * @private 90 | */ 91 | Logger.prototype._showUrl = function(url) { 92 | return Logger.showUrl(url); 93 | }; 94 | 95 | /** 96 | * Метод для обработки ссылок, передаваемых в лог. Можно переопределять. По умолчанию не выполняет никаких действий. 97 | * @name ya.music.Audio.Logger#showUrl 98 | * @param {String} url Ссылка. 99 | * @returns {String} ссылку. 100 | */ 101 | Logger.showUrl = function(url) { 102 | return url; 103 | }; 104 | 105 | LEVELS.forEach(function(level) { 106 | Logger.prototype[level] = function() { 107 | var args = [].slice.call(arguments); 108 | args.unshift(this.channel); 109 | args.unshift(level); 110 | Logger.log.apply(Logger, args); 111 | }; 112 | }); 113 | 114 | // ================================================================= 115 | 116 | // Запись данных в лог 117 | 118 | // ================================================================= 119 | 120 | /** 121 | * Сделать запись в лог. 122 | * @param {String} level Уровень лога. 123 | * @param {String} channel Канал. 124 | * @param {Object} context Контекст вызова. 125 | * @param {...*} [args] Дополнительные аргументы. 126 | */ 127 | Logger.log = function(level, channel, context) { 128 | var data = [].slice.call(arguments, 3).map(function(dumpItem) { 129 | return dumpItem && dumpItem._logger && dumpItem._logger() || dumpItem; 130 | }); 131 | 132 | var logEntry = { 133 | timestamp: +new Date(), 134 | level: level, 135 | channel: channel, 136 | context: context, 137 | message: data 138 | }; 139 | 140 | if (Logger.ignores[channel] || Logger.logLevels.indexOf(level) === -1) { 141 | return; 142 | } 143 | 144 | Logger._dumpEntry(logEntry); 145 | }; 146 | 147 | /** 148 | * Запись в логе. 149 | * @typedef {Object} Audio.Logger.LogEntry 150 | * @property {Number} timestamp Время в timestamp формате. 151 | * @property {String} level Уровень лога. 152 | * @property {String} channel Канал. 153 | * @property {Object} context Контекст вызова. 154 | * @property {Array} message Дополнительные аргументы. 155 | * 156 | * @private 157 | */ 158 | 159 | /** 160 | * Записать сообщение лога в консоль. 161 | * @param {ya.music.Audio.Logger~LogEntry} logEntry Сообщение лога. 162 | * @private 163 | */ 164 | Logger._dumpEntry = function(logEntry) { 165 | try { 166 | var level = logEntry.level; 167 | 168 | var name = logEntry.context && (logEntry.context.taskName || logEntry.context.name); 169 | var context = logEntry.context && (logEntry.context._logger ? logEntry.context._logger() : ""); 170 | 171 | if (typeof console[level] !== "function") { 172 | console.log.apply(console, [ 173 | level.toUpperCase(), 174 | Logger._formatTimestamp(logEntry.timestamp), 175 | "[" + logEntry.channel + (name ? ":" + name : "") + "]", 176 | context 177 | ].concat(logEntry.message)); 178 | } else { 179 | console[level].apply(console, [ 180 | Logger._formatTimestamp(logEntry.timestamp), 181 | "[" + logEntry.channel + (name ? ":" + name : "") + "]", 182 | context 183 | ].concat(logEntry.message)); 184 | } 185 | } catch(e) { 186 | } 187 | }; 188 | 189 | /** 190 | * Вспомогательная функция форматирования даты для вывода в коносоль. 191 | * @param timestamp 192 | * @returns {string} 193 | * @private 194 | */ 195 | Logger._formatTimestamp = function(timestamp) { 196 | var date = new Date(timestamp); 197 | var ms = date.getMilliseconds(); 198 | ms = ms > 100 ? ms : ms > 10 ? "0" + ms : "00" + ms; 199 | return date.toLocaleTimeString() + "." + ms; 200 | }; 201 | 202 | module.exports = Logger; 203 | -------------------------------------------------------------------------------- /src/lib/async/events.js: -------------------------------------------------------------------------------- 1 | var merge = require('../data/merge'); 2 | 3 | var LISTENERS_NAME = "_listeners"; 4 | var MUTE_OPTION = "_muted"; 5 | 6 | // ================================================================= 7 | 8 | // Конструктор 9 | 10 | // ================================================================= 11 | 12 | /** 13 | * @classdesc Диспетчер событий. 14 | * @class 15 | * @exported ya.music.lib.Events 16 | */ 17 | var Events = function() { 18 | /** 19 | * Контейнер для списков слушателей событий. 20 | * @alias Audio.Events#_listeners 21 | * @type {Object.>} 22 | * @private 23 | */ 24 | this[LISTENERS_NAME] = {}; 25 | 26 | /** Флаг включения/выключения событий 27 | * @alias Events#_mutes 28 | * @type {Boolean} 29 | * @private 30 | */ 31 | this[MUTE_OPTION] = false; 32 | }; 33 | 34 | // ================================================================= 35 | 36 | // Всяческий сахар 37 | 38 | // ================================================================= 39 | 40 | /** 41 | * Расширить произвольный класс свойствами диспетчера событий. 42 | * @param {Function} classConstructor Конструктор класса. 43 | * @returns {Function} тот же конструктор класса, расширенный свойствами диспетчера событий. 44 | * @static 45 | */ 46 | Events.mixin = function(classConstructor) { 47 | merge(classConstructor.prototype, Events.prototype, true); 48 | return classConstructor; 49 | }; 50 | 51 | /** 52 | * Расширить произвольный объект свойствами диспетчера событий. 53 | * @param {Object} object Объект. 54 | * @returns {Object} тот же объект, расширенный свойствами диспетчера событий. 55 | */ 56 | Events.eventize = function(object) { 57 | merge(object, Events.prototype, true); 58 | Events.call(object); 59 | return object; 60 | }; 61 | 62 | // ================================================================= 63 | 64 | // Подписка и отписка от событий 65 | 66 | // ================================================================= 67 | 68 | /** 69 | * Подписаться на событие (цепочный метод). 70 | * @param {String} event Имя события. 71 | * @param {function} callback Обработчик события. 72 | * @returns {Events} ссылку на контекст. 73 | */ 74 | Events.prototype.on = function(event, callback) { 75 | if (!this[LISTENERS_NAME][event]) { 76 | this[LISTENERS_NAME][event] = []; 77 | } 78 | 79 | this[LISTENERS_NAME][event].push(callback); 80 | return this; 81 | }; 82 | 83 | /** 84 | * Отписаться от события (цепочный метод). 85 | * @param {String} event Имя события. 86 | * @param {function} callback Обработчик события. 87 | * @returns {Events} ссылку на контекст. 88 | */ 89 | Events.prototype.off = function(event, callback) { 90 | if (!this[LISTENERS_NAME][event]) { 91 | return this; 92 | } 93 | 94 | if (!callback) { 95 | delete this[LISTENERS_NAME][event]; 96 | return this; 97 | } 98 | 99 | var callbacks = this[LISTENERS_NAME][event]; 100 | for (var k = 0, l = callbacks.length; k < l; k++) { 101 | if (callbacks[k] === callback || callbacks[k].callback === callback) { 102 | callbacks.splice(k, 1); 103 | if (!callbacks.length) { 104 | delete this[LISTENERS_NAME][event]; 105 | } 106 | break; 107 | } 108 | } 109 | 110 | return this; 111 | }; 112 | 113 | /** 114 | * Подписаться на событие и отписаться сразу после его первого возникновения (цепочный метод). 115 | * @param {String} event Имя события. 116 | * @param {function} callback Обработчик события. 117 | * @returns {Events} ссылку на контекст. 118 | */ 119 | Events.prototype.once = function(event, callback) { 120 | var self = this; 121 | 122 | var wrapper = function() { 123 | self.off(event, wrapper); 124 | callback.apply(this, arguments); 125 | }; 126 | 127 | wrapper.callback = callback; 128 | self.on(event, wrapper); 129 | 130 | return this; 131 | }; 132 | 133 | /** 134 | * Отписаться от всех слушателей событий (цепочный метод). 135 | * @returns {Events} ссылку на контекст. 136 | */ 137 | Events.prototype.clearListeners = function() { 138 | for (var key in this[LISTENERS_NAME]) { 139 | if (this[LISTENERS_NAME].hasOwnProperty(key)) { 140 | delete this[LISTENERS_NAME][key]; 141 | } 142 | } 143 | 144 | return this; 145 | }; 146 | 147 | // ================================================================= 148 | 149 | // Триггер событий 150 | 151 | // ================================================================= 152 | 153 | /** 154 | * Запустить событие (цепочный метод). 155 | * @param {String} event Имя события. 156 | * @param {...args} args Параметры для передачи вместе с событием. 157 | * @returns {Events} ссылку на контекст. 158 | * @private 159 | */ 160 | Events.prototype.trigger = function(event, args) { 161 | if (this[MUTE_OPTION]) { 162 | return this; 163 | } 164 | 165 | args = [].slice.call(arguments, 1); 166 | 167 | if (event !== "*") { 168 | Events.prototype.trigger.apply(this, ["*", event].concat(args)); 169 | } 170 | 171 | if (!this[LISTENERS_NAME][event]) { 172 | return this; 173 | } 174 | 175 | var callbacks = [].concat(this[LISTENERS_NAME][event]); 176 | for (var k = 0, l = callbacks.length; k < l; k++) { 177 | callbacks[k].apply(null, args); 178 | } 179 | 180 | return this; 181 | }; 182 | 183 | /** 184 | * Делегировать все события другому диспетчеру событий (цепочный метод). 185 | * @param {Events} acceptor Получатель событий. 186 | * @returns {Events} ссылку на контекст. 187 | * @private 188 | */ 189 | Events.prototype.pipeEvents = function(acceptor) { 190 | this.on("*", Events.prototype.trigger.bind(acceptor)); 191 | return this; 192 | }; 193 | 194 | // ================================================================= 195 | 196 | // Включение/выключение триггера событий 197 | 198 | // ================================================================= 199 | 200 | /** 201 | * Остановить запуск событий (цепочный метод). 202 | * @returns {Events} ссылку на контекст. 203 | */ 204 | Events.prototype.muteEvents = function() { 205 | this[MUTE_OPTION] = true; 206 | return this; 207 | }; 208 | 209 | /** 210 | * Возобновить запуск событий (цепочный метод). 211 | * @returns {Events} ссылку на контекст. 212 | */ 213 | Events.prototype.unmuteEvents = function() { 214 | delete this[MUTE_OPTION]; 215 | return this; 216 | }; 217 | 218 | module.exports = Events; 219 | -------------------------------------------------------------------------------- /tutorial/contrib.md: -------------------------------------------------------------------------------- 1 | Контрибьюторам 2 | ============== 3 | Если вы решили помочь в разработке данной библиотеки или вам требуется внести некоторые изменения для своих нужд, эта документация будет вам полезна. 4 | 5 | Code-style 6 | ---------- 7 | Есть несколько требований к стилю кода в данном проекте: 8 | 9 | #### Одна сущность - один файл 10 | Каждый класс или отдельная функция должны быть оформлены в виде отдельного файла, 11 | название которого состоит из имени этой сущности в css-case (нижний регистр, слова разделяются дефисами). 12 | 13 | #### Документация кода 14 | Все сущности должны иметь документацию в формате jsdoc. Подробнее об этом можно почитать [тут](http://usejsdoc.org/) 15 | или посмотреть примеры прямо в коде. Не обязательно подробно описывать работу функций или классов, 16 | достаточно краткого описания. Также обязательным является указание типа переменных 17 | и возвращаемых значений. 18 | 19 | В генераторе документации используется специальная директива `@exported` для указания под каким именем данный объект доступен в публичном неймспейсе (см. [src/audio.js](https://github.yandex-team.ru/music/audio/blob/mddoc/src/audio-player.js#L178)) 20 | 21 | 22 | Если требуется в коде визуально отделить один блок от другого, следует использовать вот такие вставки c описанием идущего 23 | за ними блока 24 | ``` 25 | // ================================================================= 26 | 27 | // Проверка доступности Flash-плеера 28 | 29 | // ================================================================= 30 | ``` 31 | Данный формат выбран т.к. многие IDE сворачивают многострочные комментарии, а это как раз в данном случае не нужно. 32 | Подобный комментарий не будет свернут и он хорошо выделяется. 33 | 34 | #### Форматирование 35 | - `camelCase` - все сущности в проекте именуются согласно форматированию camelCase. Имена классов начинаются с 36 | заглавной буквы, остальные сущности именуются с маленькой буквы. 37 | - `ALL_CAPS_SNAKE_CASE` - все имена констант записываются заглавными буквами, в качестве разделителя слов 38 | используется нижнее подчеркивание. 39 | - `_privateMethod` - приватные методы должны начинаться с подчеркивания. Все свойства считаются приватными и пишутся 40 | без подчеркивания. 41 | - `__boundMethod` - если требуется создавать версию метода, с привязанным контекстом, новый метод должен называться 42 | так же, как оригинальный и начинаться с 2х подчеркиваний (приватные методы в данном случае все равно начинаются с 2х 43 | подчеркиваний, а не с 3х). Такие методы следует создавать в конструкторе класса (желательно использовать именно такой 44 | подход при создании обработчиков событий). 45 | - `"Строки"` - строки заключаются в двойные кавычки. 46 | - `require('some-file')` - инструкции подключения файлов выносятся в начало файла. Имена файлов указываются в 47 | одинарных кавычках без расширения (расширение следует указывать явно только в том случае, если в имени файла есть точка). 48 | Если требуется подключать файлы json, их следует перевести в формат js, добавив в начало файла `module.exports =`. 49 | - `\n` - для переводов строк используется LF-нотация (Unix и OS X стандарт). 50 | - `;\n` - каждая инструкция должна завершаться точкой с запятой и переводом строки. 51 | - `\n&&`, ... - при необходимости переноса знак операции переносится в начало строки. 52 | - ` var a;` - для форматирования отступов используется 4 пробела. 53 | - `a = b + c` - знаки равенства, математических операций и сравнения следует отделять пробелами. 54 | - `var someFunction = function() {\n ... \n};` - функции объявляются через `var`. Не следует использовать именованные 55 | функции. Между объявлением функции/метода и скобками пробела быть не должно. Фигурные скобки ставятся на той же 56 | строке и отделяются пробелом. После закрывающей фигурной скобки следует ставить точку с запятой. После открывающей 57 | и перед закрывающей фигурной скобкой ставится перевод строки. Запись в одну строку допускается только в случае, 58 | если функция/метод содержит ровно 1 инструкцию. 59 | - `if (true) {\n ... \n} else {\n ... \n}` - в конструкции if-else не допускается опускание фигурных скобок. Фигурные 60 | скобки ставятся на той же строке и отделяются пробелом. После открывающей и перед закрывающей фигурной скобкой 61 | ставится перевод строки. 62 | - `<-- 120 -->` - длина строки кода или комментария не должна превышать 120 символов. 63 | 64 | #### Циклы (на графе) 65 | Граф зависимостей проекта не должен содержать циклов. Поясню: нельзя делать так, чтобы 66 | - файл **A** использует файл **B** 67 | - файл **B** использует файл **A** 68 | 69 | Данную конструкцию следует исправлять подобным образом: 70 | - файл **A** использует файлы **B** и **С** 71 | - файл **B** использует файл **C** 72 | 73 | Сборка javascipt 74 | ---------------- 75 | Сборка библиотеки производится с помощью npm-пакетов [browserify](https://www.npmjs.com/package/browserify) и [uglify-js](https://www.npmjs.com/package/uglify-js). 76 | Для того чтобы подготовить окружение для сборки библиотеки, требуется установить [node-js](https://nodejs.org/en/) и выполнить `npm install` в корне репозитория. 77 | Сам процесс сборки доступен в двух вариантах: с помощью утилиты make (основой метод) и с помощью библиотеки [grunt](http://gruntjs.com/). 78 | 79 | ### Makefile 80 | Является основным методом сборки. Доступные команды: 81 | 82 | - **make all** - делает полную сборку библиотеки 83 | - **make clean** - удаляет каталог сборки 84 | - **make build** - собирает библиотеку 85 | - **make minify** - собирает минифицированную версию библиотеки (не пересобирает библиотеку, если она была уже собрана через make build) 86 | 87 | Если вызывать команду **make** без аргументов, то будет выполнен сценарий **make all**. 88 | Перед тем как делать pull request, следует сделать полную сборку библиотеки с помощью make. 89 | 90 | ### Grunt 91 | Запасной вариант сборки для тех, у кого по каким-то причинам нет возможности воспользоваться утилитой make. 92 | Чтобы использовать сборку через grunt, требуется установить глобально пакет [grunt-cli](https://www.npmjs.com/package/grunt-cli) (`npm install -g grunt-cli`). 93 | Доступные команды: 94 | 95 | - **grunt all** - делает полную сборку библиотеки 96 | - **grunt clean** - удаляет каталог сборки 97 | - **grunt build** - собирает библиотеку 98 | 99 | Без аргументов **grunt** выполняет сценарий **grunt all**. 100 | 101 | 102 | Сборка Flash 103 | ------------ 104 | Сборка Flash-плеера в автоматическом режиме не доступна. Для ручной сборки требуется настроить какой-либо сборщик (наиболее удобные: IntelliJ IDEA, FlashDevelop). Параметры, используемые для сборки: FlexSDK 3.6.0, целевая версия плеера - 9, основной класс - AudioManager, имя собранного файла - player-2_1.swf 105 | 106 | -------------------------------------------------------------------------------- /tutorial/quick-start.md: -------------------------------------------------------------------------------- 1 | Быстрый старт 2 | ============= 3 | В данном разделе на примере создания простого плеера проиллюстрированы основные принципы работы с YandexAudio. 4 | 5 | Для начала создадим html каркас будущего плеера. Нам понадобятся кнопки play, шкала с позицией воспроизведения 6 | и шкала громкости. Также для инициализации flash-плеера, отображения ошибок и блокировки управления потребуется оверлей. 7 | 8 | ***index.html*** 9 | ```html 10 |
11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 |
19 | 20 |
21 |
22 |
23 | 24 |
25 |
26 | 27 | 28 | 29 | ``` 30 | 31 | Теперь инициализируем всю эту структуру и создадим экземпляр плеера. 32 | 33 | ***index.js*** 34 | ```javascript 35 | var AudioPlayer = ya.music.Audio; 36 | 37 | var dom = { 38 | player: document.querySelector(".player"), 39 | 40 | play: document.querySelector(".controls__play"), 41 | 42 | progress: { 43 | bar: document.querySelector(".progress"), 44 | loaded: document.querySelector(".progress__loaded"), 45 | current: document.querySelector(".progress__current") 46 | }, 47 | 48 | volume: { 49 | bar: document.querySelector(".volume"), 50 | value: document.querySelector(".volume__bar"), 51 | }, 52 | 53 | overlay: document.querySelector(".overlay") 54 | }; 55 | 56 | // Предоставим плееру самому решать, какой тип реализации использовать 57 | var audioPlayer = new AudioPlayer(null, dom.overlay); 58 | 59 | audioPlayer.initPromise().then(function() { 60 | // Скрываем оверлей, кнопки управления становятся доступны 61 | dom.overlay.classList.add("overlay_hidden"); 62 | }, function(err) { 63 | // Показываем ошибку инициализации в оверлее 64 | dom.overlay.innerHTML = err.message; 65 | dom.overlay.classList.add("overlay_error"); 66 | }); 67 | ``` 68 | 69 | Настроим отображение статуса плеера. Для простого плеера нам достаточно знать, запущено воспроизведение или нет. 70 | 71 | ```javascript 72 | audioPlayer.on(ya.music.Audio.EVENT_STATE, function(state) { 73 | if (state === ya.music.Audio.STATE_PLAYING) { 74 | dom.player.classList.add("player_playing"); 75 | } else { 76 | dom.player.classList.remove("player_playing"); 77 | } 78 | }); 79 | ``` 80 | 81 | Теперь настроим обновление прогресс-бара. В нем предусмотрены две шкалы - шкала загрузки и шкала текущей 82 | позиции воспроизведения. 83 | 84 | ```javascript 85 | audioPlayer.on(ya.music.Audio.EVENT_PROGRESS, function(timings) { 86 | dom.progress.loaded.style.width = (timings.loaded / timings.duration * 100).toFixed(2) + "%"; 87 | dom.progress.current.style.width = (timings.position / timings.duration * 100).toFixed(2) + "%"; 88 | }); 89 | ``` 90 | 91 | Аналогично будет работать шкала громкости. 92 | 93 | ```javascript 94 | var updateVolume = function(volume) { 95 | dom.volume.value.style.height = (volume * 100).toFixed(2) + "%"; 96 | }; 97 | audioPlayer.on(ya.music.Audio.EVENT_VOLUME, updateVolume); 98 | 99 | // Отображаем начальную громкость 100 | audioPlayer.initPromise().then(function() { 101 | updateVolume(audioPlayer.getVolume()); 102 | }); 103 | ``` 104 | 105 | Теперь нужно настроить взаимодействие плеера с пользователем. Начнем с запуска воспроизведения. 106 | 107 | ```javascript 108 | var trackUrls = [ 109 | "http://some.domain.and.zone/Здесь-могла-быть-ваша-реклама-1.mp3", 110 | "http://some.domain.and.zone/Здесь-могла-быть-ваша-реклама-2.mp3", 111 | "http://some.domain.and.zone/Здесь-могла-быть-ваша-реклама-3.mp3" 112 | ]; 113 | 114 | var trackIndex = 0; 115 | 116 | var startPlay = function() { 117 | audioPlayer.play(trackUrls[trackIndex]); 118 | }; 119 | 120 | dom.play.addEventListener("click", function() { 121 | var state = audioPlayer.getState(); 122 | 123 | switch (state) { 124 | case ya.music.Audio.STATE_PLAYING: 125 | audioPlayer.pause(); 126 | break; 127 | 128 | case ya.music.Audio.STATE_PAUSED: 129 | audioPlayer.resume(); 130 | break; 131 | 132 | default: 133 | startPlay(); 134 | break; 135 | } 136 | }); 137 | 138 | audioPlayer.on(ya.music.Audio.EVENT_ENDED, function() { 139 | trackIndex++; 140 | 141 | if (trackIndex < trackUrls.length) { 142 | startPlay(); 143 | } 144 | }); 145 | ``` 146 | 147 | Добавим немножко удобства для пользователей: сделаем автозагрузку следующего трека после того, как текущий загрузился. 148 | Для этого потребуется немного изменить функцию `startPlay` и отслеживать момент загрузки трека: 149 | 150 | ```javascript 151 | var startPlay = function() { 152 | var track = trackUrls[trackIndex]; 153 | if (audioPlayer.isPreloaded(track)) { 154 | audioPlayer.playPreloaded(track); 155 | } else { 156 | audioPlayer.play(track); 157 | } 158 | }; 159 | 160 | audioPlayer.on(ya.music.Audio.EVENT_LOADED, function() { 161 | if (trackIndex + 1 < trackUrls.length) { 162 | audioPlayer.preload(trackUrls[trackIndex + 1]); 163 | } 164 | }); 165 | ``` 166 | 167 | Осталось только настроить навигацию по треку и регулирование громкости: 168 | ```javascript 169 | var offsetLeft = function(node) { 170 | var offset = node.offsetLeft; 171 | if (node.offsetParent) { 172 | offset += offsetLeft(node.offsetParent); 173 | } 174 | return offset; 175 | }; 176 | 177 | var offsetTop = function(node) { 178 | var offset = node.offsetTop; 179 | if (node.offsetParent) { 180 | offset += offsetTop(node.offsetParent); 181 | } 182 | return offset; 183 | }; 184 | 185 | dom.progress.bar.addEventListener("click", function(evt) { 186 | var fullWidth = dom.progress.bar.offsetWidth; 187 | var offset = offsetLeft(dom.progress.bar); 188 | 189 | var relativePosition = Math.max(0, Math.min(1, ((evt.pageX || evt.screenX) - offset) / fullWidth)); 190 | var duration = audioPlayer.getDuration(); 191 | 192 | audioPlayer.setPosition(duration * relativePosition); 193 | }); 194 | 195 | dom.volume.bar.addEventListener("click", function(evt) { 196 | var fullHeight = dom.volume.bar.offsetHeight; 197 | var offset = offsetTop(dom.volume.bar); 198 | 199 | // тут мы делаем "1 -" т.к. громость принято отмерять снизу, а не сверху 200 | var volume = 1 - Math.max(0, Math.min(1, ((evt.pageY || evt.screenY) - offset) / fullHeight)); 201 | audioPlayer.setVolume(volume); 202 | }); 203 | ``` 204 | 205 | Полный код можно [посмотреть тут](https://github.yandex-team.ru/music/audio/tree/master/examples/quick-start). 206 | Рабочий пример кода можно [посмотреть тут](https://music.yandex.ru/api/audio/examples/quick-start/index.html). 207 | -------------------------------------------------------------------------------- /src/IAudioImplementation.jsdoc: -------------------------------------------------------------------------------- 1 | // ================================================================= 2 | 3 | // Интерфейс 4 | 5 | // ================================================================= 6 | 7 | /** 8 | * Интерфейс внутренней реализации плеера. 9 | * @typedef {function} IAudioImplementation 10 | * @kind class 11 | * 12 | * @extends Events 13 | * 14 | * @param {HTMLElement} [overlay] - место для встраивания плеера (актуально только для Flash-плеера) 15 | * 16 | * @property {string} type - Тип плеера 17 | * @property {Boolean} available - Доступность реализации 18 | * 19 | * @fires IAudioImplementation#EVENT_PLAY 20 | * @fires IAudioImplementation#EVENT_ENDED 21 | * @fires IAudioImplementation#EVENT_VOLUME 22 | * @fires IAudioImplementation#EVENT_CRASHED 23 | * @fires IAudioImplementation#EVENT_SWAP 24 | * 25 | * @fires IAudioImplementation#EVENT_STOP 26 | * @fires IAudioImplementation#EVENT_PAUSE 27 | * @fires IAudioImplementation#EVENT_PROGRESS 28 | * @fires IAudioImplementation#EVENT_LOADING 29 | * @fires IAudioImplementation#EVENT_LOADED 30 | * @fires IAudioImplementation#EVENT_ERROR 31 | * 32 | * @abstract 33 | * @private 34 | */ 35 | 36 | // ================================================================= 37 | 38 | // События 39 | 40 | // ================================================================= 41 | 42 | /** Событие начала воспроизведения 43 | * @event IAudioImplementation#EVENT_PLAY 44 | */ 45 | /** Событие завершения воспроизведения 46 | * @event IAudioImplementation#EVENT_ENDED 47 | */ 48 | /** Событие изменения громкости 49 | * @event IAudioImplementation#EVENT_VOLUME 50 | * @param {Number} volume - громкость 51 | */ 52 | /** Событие краха плеера 53 | * @event IAudioImplementation#EVENT_CRASHED 54 | */ 55 | /** Событие переключения активного плеера и прелоадера 56 | * @event IAudioImplementation#EVENT_SWAP 57 | */ 58 | 59 | /** Событие остановки воспроизведения 60 | * @event IAudioImplementation#EVENT_STOP 61 | */ 62 | /** Событие начала воспроизведения 63 | * @event IAudioImplementation#EVENT_PAUSE 64 | */ 65 | /** Событие обновления позиции воспроизведения/загруженной части 66 | * @event IAudioImplementation#EVENT_PROGRESS 67 | * @param {Audio~AudioPlayerTimes} times - информация о временных данных трека 68 | */ 69 | /** Событие начала загрузки трека 70 | * @event IAudioImplementation#EVENT_LOADING 71 | */ 72 | /** Событие завершения загрузки трека 73 | * @event IAudioImplementation#EVENT_LOADED 74 | */ 75 | /** Событие ошибки воспроизведения 76 | * @event IAudioImplementation#EVENT_ERROR 77 | */ 78 | 79 | // ================================================================= 80 | 81 | // Методы управления воспроизведением 82 | 83 | // ================================================================= 84 | 85 | /** 86 | * Обещание, которое разрешается при завершении инициализации 87 | * @member IAudioImplementation#whenReady 88 | * @type {Promise} 89 | */ 90 | 91 | /** 92 | * Проиграть трек 93 | * @method IAudioImplementation#play 94 | * @param {String} src - ссылка на трек 95 | * @param {Number} [duration] - Длительность трека (не используется) 96 | * @abstract 97 | */ 98 | 99 | /** 100 | * Поставить трек на паузу 101 | * @method IAudioImplementation#pause 102 | * @abstract 103 | */ 104 | 105 | /** 106 | * Снять трек с паузы 107 | * @method IAudioImplementation#resume 108 | * @abstract 109 | */ 110 | 111 | /** 112 | * Остановить воспроизведение и загрузку трека 113 | * @method IAudioImplementation#stop 114 | * @param {int} [offset=0] - 0: для текущего загрузчика, 1: для следующего загрузчика 115 | * @abstract 116 | */ 117 | 118 | /** 119 | * Получить позицию воспроизведения 120 | * @method IAudioImplementation#getPosition 121 | * @returns {number} 122 | * @abstract 123 | */ 124 | 125 | /** 126 | * Установить текущую позицию воспроизведения 127 | * @method IAudioImplementation#setPosition 128 | * @param {number} position 129 | * @abstract 130 | */ 131 | 132 | /** 133 | * Получить длительность трека 134 | * @method IAudioImplementation#getDuration 135 | * @param {int} [offset=0] - 0: текущий загрузчик, 1: следующий загрузчик 136 | * @returns {number} 137 | * @abstract 138 | */ 139 | 140 | /** 141 | * Получить длительность загруженной части трека 142 | * @method IAudioImplementation#getLoaded 143 | * @param {int} [offset=0] - 0: текущий загрузчик, 1: следующий загрузчик 144 | * @returns {number} 145 | * @abstract 146 | */ 147 | 148 | /** 149 | * Получить максимально возможную точку перемотки 150 | * @method IAudioImplementation#getMaxSeekablePosition 151 | * @param {int} [offset=0] - 0: текущий загрузчик, 1: следующий загрузчик 152 | * @returns {number} 153 | * @abstract 154 | */ 155 | 156 | /** 157 | * Получить текущее значение громкости 158 | * @method IAudioImplementation#getVolume 159 | * @returns {number} 160 | * @abstract 161 | */ 162 | 163 | /** 164 | * Установить значение громкости 165 | * @method IAudioImplementation#setVolume 166 | * @param {number} volume - желаемая громкость 167 | * @abstract 168 | */ 169 | 170 | // ================================================================= 171 | 172 | // Методы предзагрузчика 173 | 174 | // ================================================================= 175 | 176 | /** 177 | * Предзагрузить трек 178 | * @method IAudioImplementation#preload 179 | * @param {String} src - Ссылка на трек 180 | * @param {Number} [duration] - Длительность трека (не используется) 181 | * @param {int} [offset=1] - 0: текущий загрузчик, 1: следующий загрузчик 182 | * @abstract 183 | */ 184 | 185 | /** 186 | * Проверить что трек предзагружается 187 | * @method IAudioImplementation#isPreloaded 188 | * @param {String} src - ссылка на трек 189 | * @param {int} [offset=1] - 0: текущий загрузчик, 1: следующий загрузчик 190 | * @returns {boolean} 191 | * @abstract 192 | */ 193 | 194 | /** 195 | * Проверить что трек начал предзагружаться 196 | * @method IAudioImplementation#isPreloading 197 | * @param {String} src - ссылка на трек 198 | * @param {int} [offset=1] - 0: текущий загрузчик, 1: следующий загрузчик 199 | * @returns {boolean} 200 | * @abstract 201 | */ 202 | 203 | /** 204 | * Запустить воспроизведение предзагруженного трека 205 | * @method IAudioImplementation#playPreloaded 206 | * @param {int} [offset=1] - 0: текущий загрузчик, 1: следующий загрузчик 207 | * @returns {boolean} -- доступность данного действия 208 | * @abstract 209 | */ 210 | 211 | // ================================================================= 212 | 213 | // Методы получения данных 214 | 215 | // ================================================================= 216 | 217 | /** 218 | * Получить ссылку на трек 219 | * @method IAudioImplementation#getSrc 220 | * @param {int} [offset=0] - 0: текущий загрузчик, 1: следующий загрузчик 221 | * @returns {String|Boolean} -- Ссылка на трек или false, если нет загружаемого трека 222 | * @abstract 223 | */ 224 | 225 | /** 226 | * Проверить доступен ли программный контроль громкости 227 | * @method IAudioImplementation#isDeviceVolume 228 | * @returns {boolean} 229 | * @abstract 230 | */ 231 | 232 | /** 233 | * Проверить доступность воcпроизведения без пользовательского взаимодействия 234 | * @method IAudioImplementation#isAutoplayable 235 | * @returns {boolean} 236 | * @abstract 237 | */ 238 | -------------------------------------------------------------------------------- /tutorial/corner-case.md: -------------------------------------------------------------------------------- 1 | Подводные камни 2 | =============== 3 | 4 | Данное руководство объясняет некоторые особенности работы библиотеки. Как html5 audio, так и flash являются весьма 5 | нестабильными технологиями и имеют разную поддержку в разных браузерах. Большую часть этих проблем данная библиотека 6 | решает, но, тем не менее, остаются вещи, которые нельзя решить с помощью js или которые являются неочевидными для разработчиков. 7 | 8 | Flash 9 | ----- 10 | 11 | ### Flash и оверлей 12 | 13 | Существует по меньшей мере две проблемы с flash, которые невозможно 100% продиагностировать с помощью js: 14 | - узнать точную версию flash-плеера; 15 | - узнать, есть ли блокировщик flash в браузере. 16 | 17 | Узнать точную версию flash нельзя, т.к. некоторые плагины для браузеров выводят совершенно неадекватные номера версий. 18 | Встречались случаи, когда номер версии был 99.999.999 или наоборот 0.0.0. С блокировщиками flash ситуация усложняется - 19 | некоторые блокировщики специально стараются замаскировать свое присутствие, другие не очень корректно 20 | производят блокировку (например, позволяют flash-апплету полностью загрузиться и даже выполнить какой-нибудь код и лишь 21 | после этого блокируют их). Более того, если flash-апплет невидим, то у пользователя нет никакой возможности 22 | узнать о факте блокировки и тем более нет возможности разблокировать его. 23 | 24 | Для решения данной проблемы конструктор плеера принимает в качестве необязательного аргумента html-элемент для 25 | отображения оверлея. В этот оверлей помещается прозрачный flash-апплет плеера. Это позволяет в случае необходимости 26 | показать пользователю, что flash-блокировщик сработал некорректно и его нужно отключить. Большая часть блокировщиков позволяет 27 | отменить блокировку простым кликом по данному html-элементу, поэтому плеер прослушивает событие клика. Если после 28 | наступления этого события flash-апплет по-прежнему оказывается недоступен, плеер пытается применить другую технологию воспроизведения или перезагружает апплет. 29 | 30 | ***ВАЖНО!*** После того как плеер был успешно инициализирован, не нужный более оверлей нельзя просто удалить из DOM дерева. 31 | Также нельзя скрыть его с помощью стиля `display: none`. Это приведет к тому, что плеер перейдет в нерабочее состояние. 32 | Для того чтобы скрыть ненужный оверлей, можно, например, вынести его за пределы страницы следующим css-стилем: 33 | 34 | ```css 35 | position: absolute; 36 | top: -9999px; 37 | left: -9999px; 38 | ``` 39 | 40 | ### Некоторые особенности flash 41 | Во flash-реализации плеера есть несколько особенностей, про которые стоит знать. 42 | 43 | 1. flash-апплет является синглтоном. При создании любого количества плееров с внутренним типом flash - апплет 44 | будет загружен и включен в страницу только один раз. Сам апплет является чем-то вроде фабрики и моста одновременно. 45 | Он создает во flash окружении экземпляры flash-плееров и коммутирует запросы и события, связанные с этими плеерами. 46 | У этого решения есть как плюсы, так и минусы: 47 | Плюсы: 48 | - существенно меньше потребление памяти при использовании нескольких плееров на странице; 49 | - при наличии flash-блокеров инициализация требуется всего один раз для любого количества плееров. 50 | Минус: 51 | - при крахе апплета "умрут" сразу все плееры, которые используют flash-реализацию (такое пока не наблюдалось, но 52 | вероятность все же существует). 53 | 54 | 2. Текущая реализация flash-плеера достаточно проста и потому имеет одно ограничение: нельзя ставить позицию 55 | воспроизведения дальше загруженной части трека. При установке позиции воспроизведения следует всегда считывать 56 | возвращаемое значение - оно будет отражать реальную позицию, на которую было установлено воспроизведение (или как 57 | вариант полагаться только на события обновления позиции воспроизведения). 58 | 59 | HTML5 60 | ----- 61 | 62 | ### HTML5 и мобильные браузеры 63 | В мобильных браузерах есть сразу несколько проблем с воспроизведением звука в html5 audio элементе. 64 | 65 | 1. Элементы audio начинают работать и воспроизводить звук, только если команда play была вызвана внутри обработчика 66 | пользовательского события. Это поведние присутствует также и в большинстве десктопных браузеров. Задачу начальной 67 | инициализации API берет на себя, прослушивая события keydown, mousedown, и touchstart на объекте body. 68 | Это требуется выполнить всего лишь один раз, далее можно спокойно пользоваться элементом. 69 | 70 | 2. Мобильные браузеры работают некорректно, когда на странице присутствует два и более элементов audio: 71 | 72 | - Только один элемент может быть активным. Если запустить воспроизведение во втором, то в первом оно остановится. 73 | - После такого сценария остановки первый элемент "умирает". Повторная попытка запустить воспроизведение в этом 74 | элементе не даст результатов. От этого помогает только вызов на первом элементе 75 | метода load. 76 | - Даже если трек полностью закешировался и ссылка в элементе audio не менялась, повторный вызов метода load 77 | повторно загрузит трек снуля. Кеширование не применяется. 78 | 79 | Данная библиотека решает перечисленные выше проблемы - при запуске воспроизведения трека 80 | скрипт следит за процессом загрузки и обновления позиции воспроизведения и, если воспроизведение на самом деле не 81 | начинается, то скрипт перезагружает трек и запускает воспроизведение заново с точки, на которой оно остановилось. 82 | При отладке работы плеера следует учитывать данное поведение. 83 | 84 | 3. Некоторые мобильные браузеры запрещают элементу audio изменять громкость. Для того чтобы проверить, есть ли 85 | возможность программного изменения громкости, сделан метод isDeviceVolume. 86 | 87 | ### Web Audio API 88 | Технология Web Audio API самая новая из всех перечисленных, но самая перспективная. Однако у данной технологии есть ряд недостатков: 89 | 90 | 1. Для воспроизведения треков со стороннего домена с помощью Web Audio API требуется, чтобы медиа-файлы приходили 91 | с правильным заголовком Access-Control-Allow-Origin, иначе Web Audio API не сможет получить доступ к данным трека. При 92 | этом в разных браузерах это приводит к разным результатам: одни браузеры просто ничего не делают, в других наступает 93 | тишина. Если планируется динамически подключать Web Audio API во время воспроизведения трека, следует заранее включить 94 | режим CORS (метод toggleCrossDomain); в противном случае потребуется перезагрузить трек после включения этого режима. 95 | Более того, в FF 39 обнаружена ошибка, при которой даже перезагрузка трека не обеспечивает корректной 96 | работы Web Audio API. 97 | 98 | ***ВАЖНО!*** - при включенном режиме CORS аудио элемент не сможет загружать 99 | данные со сторонних доменов, если в ответе не будет правильного заголовка Access-Control-Allow-Origin. Не рекомендуется включать этот 100 | режим, если не планируется использование Web Audio API. 101 | 102 | 2. Web Audio API не работает в полной мере в большинстве мобильных браузеров, несмотря на то что удается создать контекст и при создании различных AudioNode 103 | не возникает никаких ошибок и нормально воспроизводится звук. Поэтому для мобильных браузеров Web Audio API превентивно 104 | отключен. 105 | 106 | 3. Web Audio API может существенно увеличить нагрузку на CPU, в т.ч. вызвать "заикание" звука. Стоит предоставить 107 | пользователю возможность отключить использование Web Audio API. 108 | -------------------------------------------------------------------------------- /src/lib/browser/audioContextMonkeyPatch.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2013 Chris Wilson 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. 14 | */ 15 | 16 | /* 17 | 18 | This monkeypatch library is intended to be included in projects that are 19 | written to the proper AudioContext spec (instead of webkitAudioContext), 20 | and that use the new naming and proper bits of the Web Audio API (e.g. 21 | using BufferSourceNode.start() instead of BufferSourceNode.noteOn()), but may 22 | have to run on systems that only support the deprecated bits. 23 | 24 | This library should be harmless to include if the browser supports 25 | unprefixed "AudioContext", and/or if it supports the new names. 26 | 27 | The patches this library handles: 28 | if window.AudioContext is unsupported, it will be aliased to webkitAudioContext(). 29 | if AudioBufferSourceNode.start() is unimplemented, it will be routed to noteOn() or 30 | noteGrainOn(), depending on parameters. 31 | 32 | The following aliases only take effect if the new names are not already in place: 33 | 34 | AudioBufferSourceNode.stop() is aliased to noteOff() 35 | AudioContext.createGain() is aliased to createGainNode() 36 | AudioContext.createDelay() is aliased to createDelayNode() 37 | AudioContext.createScriptProcessor() is aliased to createJavaScriptNode() 38 | AudioContext.createPeriodicWave() is aliased to createWaveTable() 39 | OscillatorNode.start() is aliased to noteOn() 40 | OscillatorNode.stop() is aliased to noteOff() 41 | OscillatorNode.setPeriodicWave() is aliased to setWaveTable() 42 | AudioParam.setTargetAtTime() is aliased to setTargetValueAtTime() 43 | 44 | This library does NOT patch the enumerated type changes, as it is 45 | recommended in the specification that implementations support both integer 46 | and string types for AudioPannerNode.panningModel, AudioPannerNode.distanceModel 47 | BiquadFilterNode.type and OscillatorNode.type. 48 | 49 | */ 50 | (function (global, exports, perf) { 51 | 'use strict'; 52 | 53 | function fixSetTarget(param) { 54 | if (!param) // if NYI, just return 55 | return; 56 | if (!param.setTargetAtTime) 57 | param.setTargetAtTime = param.setTargetValueAtTime; 58 | } 59 | 60 | if (window.hasOwnProperty('webkitAudioContext') && 61 | !window.hasOwnProperty('AudioContext')) { 62 | window.AudioContext = webkitAudioContext; 63 | 64 | if (!AudioContext.prototype.hasOwnProperty('createGain')) 65 | AudioContext.prototype.createGain = AudioContext.prototype.createGainNode; 66 | if (!AudioContext.prototype.hasOwnProperty('createDelay')) 67 | AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode; 68 | if (!AudioContext.prototype.hasOwnProperty('createScriptProcessor')) 69 | AudioContext.prototype.createScriptProcessor = AudioContext.prototype.createJavaScriptNode; 70 | if (!AudioContext.prototype.hasOwnProperty('createPeriodicWave')) 71 | AudioContext.prototype.createPeriodicWave = AudioContext.prototype.createWaveTable; 72 | 73 | 74 | AudioContext.prototype.internal_createGain = AudioContext.prototype.createGain; 75 | AudioContext.prototype.createGain = function() { 76 | var node = this.internal_createGain(); 77 | fixSetTarget(node.gain); 78 | return node; 79 | }; 80 | 81 | AudioContext.prototype.internal_createDelay = AudioContext.prototype.createDelay; 82 | AudioContext.prototype.createDelay = function(maxDelayTime) { 83 | var node = maxDelayTime ? this.internal_createDelay(maxDelayTime) : this.internal_createDelay(); 84 | fixSetTarget(node.delayTime); 85 | return node; 86 | }; 87 | 88 | AudioContext.prototype.internal_createBufferSource = AudioContext.prototype.createBufferSource; 89 | AudioContext.prototype.createBufferSource = function() { 90 | var node = this.internal_createBufferSource(); 91 | if (!node.start) { 92 | node.start = function ( when, offset, duration ) { 93 | if ( offset || duration ) 94 | this.noteGrainOn( when || 0, offset, duration ); 95 | else 96 | this.noteOn( when || 0 ); 97 | }; 98 | } else { 99 | node.internal_start = node.start; 100 | node.start = function( when, offset, duration ) { 101 | if( typeof duration !== 'undefined' ) 102 | node.internal_start( when || 0, offset, duration ); 103 | else 104 | node.internal_start( when || 0, offset ); 105 | }; 106 | } 107 | if (!node.stop) { 108 | node.stop = function ( when ) { 109 | this.noteOff( when || 0 ); 110 | }; 111 | } else { 112 | node.internal_stop = node.stop; 113 | node.stop = function( when ) { 114 | node.internal_stop( when || 0 ); 115 | }; 116 | } 117 | fixSetTarget(node.playbackRate); 118 | return node; 119 | }; 120 | 121 | AudioContext.prototype.internal_createDynamicsCompressor = AudioContext.prototype.createDynamicsCompressor; 122 | AudioContext.prototype.createDynamicsCompressor = function() { 123 | var node = this.internal_createDynamicsCompressor(); 124 | fixSetTarget(node.threshold); 125 | fixSetTarget(node.knee); 126 | fixSetTarget(node.ratio); 127 | fixSetTarget(node.reduction); 128 | fixSetTarget(node.attack); 129 | fixSetTarget(node.release); 130 | return node; 131 | }; 132 | 133 | AudioContext.prototype.internal_createBiquadFilter = AudioContext.prototype.createBiquadFilter; 134 | AudioContext.prototype.createBiquadFilter = function() { 135 | var node = this.internal_createBiquadFilter(); 136 | fixSetTarget(node.frequency); 137 | fixSetTarget(node.detune); 138 | fixSetTarget(node.Q); 139 | fixSetTarget(node.gain); 140 | return node; 141 | }; 142 | 143 | if (AudioContext.prototype.hasOwnProperty( 'createOscillator' )) { 144 | AudioContext.prototype.internal_createOscillator = AudioContext.prototype.createOscillator; 145 | AudioContext.prototype.createOscillator = function() { 146 | var node = this.internal_createOscillator(); 147 | if (!node.start) { 148 | node.start = function ( when ) { 149 | this.noteOn( when || 0 ); 150 | }; 151 | } else { 152 | node.internal_start = node.start; 153 | node.start = function ( when ) { 154 | node.internal_start( when || 0); 155 | }; 156 | } 157 | if (!node.stop) { 158 | node.stop = function ( when ) { 159 | this.noteOff( when || 0 ); 160 | }; 161 | } else { 162 | node.internal_stop = node.stop; 163 | node.stop = function( when ) { 164 | node.internal_stop( when || 0 ); 165 | }; 166 | } 167 | if (!node.setPeriodicWave) 168 | node.setPeriodicWave = node.setWaveTable; 169 | fixSetTarget(node.frequency); 170 | fixSetTarget(node.detune); 171 | return node; 172 | }; 173 | } 174 | } 175 | 176 | if (window.hasOwnProperty('webkitOfflineAudioContext') && 177 | !window.hasOwnProperty('OfflineAudioContext')) { 178 | window.OfflineAudioContext = webkitOfflineAudioContext; 179 | } 180 | 181 | }(window)); 182 | -------------------------------------------------------------------------------- /src/flash/flash-interface.js: -------------------------------------------------------------------------------- 1 | var Logger = require('../logger/logger'); 2 | var logger = new Logger('FlashInterface'); 3 | 4 | // ================================================================= 5 | 6 | // Конструктор 7 | 8 | // ================================================================= 9 | 10 | /** 11 | * @classdesc Описание внешнего интерфейса Flash-плеера 12 | * @param {Object} flash - swf-объект 13 | * @constructor 14 | * @private 15 | */ 16 | var FlashInterface = function(flash) { 17 | //FIXME: нужно придумать нормальный метод экспорта 18 | this.flash = ya.music.Audio._flash = flash; 19 | }; 20 | 21 | // ================================================================= 22 | 23 | // Общение с Flash-плеером 24 | 25 | // ================================================================= 26 | 27 | /** 28 | * Вызвать метод Flash-плеера 29 | * @param {String} fn - название метода 30 | * @returns {*} 31 | * @private 32 | */ 33 | FlashInterface.prototype._callFlash = function(fn) { 34 | //DEV && logger.debug(this, fn, arguments); 35 | 36 | try { 37 | return this.flash.call.apply(this.flash, arguments); 38 | } catch(e) { 39 | logger.error(this, "_callFlashError", e, arguments[0], arguments[1], arguments[2]); 40 | return null; 41 | } 42 | }; 43 | 44 | /** 45 | * Проверка обратной связи с Flash-плеером 46 | * @throws Ошибка доступа к Flash-плееру 47 | * @private 48 | */ 49 | FlashInterface.prototype._heartBeat = function() { 50 | this._callFlash("heartBeat", -1); 51 | }; 52 | 53 | /** 54 | * Добавить новый плеер 55 | * @returns {int} -- id нового плеера 56 | * @private 57 | */ 58 | FlashInterface.prototype._addPlayer = function() { 59 | return this._callFlash("addPlayer", -1); 60 | }; 61 | 62 | // ================================================================= 63 | 64 | // Методы управления плеером 65 | 66 | // ================================================================= 67 | 68 | /** 69 | * Установить громкость 70 | * @param {int} id - id плеера 71 | * @param {Number} volume - желаемая громкость 72 | */ 73 | FlashInterface.prototype.setVolume = function(id, volume) { 74 | this._callFlash("setVolume", -1, volume); 75 | }; 76 | 77 | /** 78 | * Получить значение громкости 79 | * @returns {Number} 80 | */ 81 | FlashInterface.prototype.getVolume = function() { 82 | return this._callFlash("getVolume", -1); 83 | }; 84 | 85 | /** 86 | * Запустить воспроизведение трека 87 | * @param {int} id - id плеера 88 | * @param {String} src - ссылка на трек 89 | * @param {Number} duration - длительность трека 90 | */ 91 | FlashInterface.prototype.play = function(id, src, duration) { 92 | this._callFlash("play", id, src, duration && duration * 1000); 93 | }; 94 | 95 | /** 96 | * Остановить воспроизведение и загрузку трека 97 | * @param {int} id - id плеера 98 | * @param {int} [offset=0] - 0: для текущего загрузчика, 1: для следующего загрузчика 99 | */ 100 | FlashInterface.prototype.stop = function(id, offset) { 101 | this._callFlash("stop", id, offset || 0); 102 | }; 103 | 104 | /** 105 | * Поставить трек на паузу 106 | * @param {int} id - id плеера 107 | */ 108 | FlashInterface.prototype.pause = function(id) { 109 | this._callFlash("pause", id); 110 | }; 111 | 112 | /** 113 | * Снять трек с паузы 114 | * @param {int} id - id плеера 115 | */ 116 | FlashInterface.prototype.resume = function(id) { 117 | this._callFlash("resume", id); 118 | }; 119 | 120 | /** 121 | * Получить позицию воспроизведения 122 | * @param {int} id - id плеера 123 | * @returns {Number} 124 | */ 125 | FlashInterface.prototype.getPosition = function(id) { 126 | return this._callFlash("getPosition", id); 127 | }; 128 | 129 | /** 130 | * Установить текущую позицию воспроизведения 131 | * @param {int} id - id плеера 132 | * @param {number} position 133 | */ 134 | FlashInterface.prototype.setPosition = function(id, position) { 135 | this._callFlash("setPosition", id, position); 136 | }; 137 | 138 | /** 139 | * Получить длительность трека 140 | * @param {int} id - id плеера 141 | * @param {int} [offset=0] - 0: текущий загрузчик, 1: следующий загрузчик 142 | * @returns {Number} 143 | */ 144 | FlashInterface.prototype.getDuration = function(id, offset) { 145 | return this._callFlash("getDuration", id, offset || 0); 146 | }; 147 | 148 | /** 149 | * Получить длительность загруженной части трека 150 | * @param {int} id - id плеера 151 | * @param {int} [offset=0] - 0: текущий загрузчик, 1: следующий загрузчик 152 | * @returns {Number} 153 | */ 154 | FlashInterface.prototype.getLoaded = function(id, offset) { 155 | return this._callFlash("getLoaded", id, offset || 0); 156 | }; 157 | 158 | /** 159 | * Получить максимально возможную точку перемотки 160 | * @param {int} id - id плеера 161 | * @param {int} [offset=0] - 0: текущий загрузчик, 1: следующий загрузчик 162 | * @returns {number} 163 | */ 164 | FlashInterface.prototype.getMaxSeekablePosition = function(id, offset) { 165 | return this.getLoaded(id, offset); 166 | }; 167 | 168 | // ================================================================= 169 | 170 | // Предзагрузка 171 | 172 | // ================================================================= 173 | 174 | /** 175 | * Предзагрузить трек 176 | * @param {int} id - id плеера 177 | * @param {String} src - ссылка на трек 178 | * @param {Number} duration - длительность трека 179 | * @param {int} [offset=0] - 0: для текущего загрузчика, 1: для следующего загрузчика 180 | * @returns {Boolean} -- возможность данного действия 181 | */ 182 | FlashInterface.prototype.preload = function(id, src, duration, offset) { 183 | return this._callFlash("preload", id, src, duration && duration * 1000, offset == null ? 1 : offset); 184 | }; 185 | 186 | /** 187 | * Проверить что трек предзагружается 188 | * @param {int} id - id плеера 189 | * @param {String} src - ссылка на трек 190 | * @param {int} [offset=1] - 0: для текущего загрузчика, 1: для следующего загрузчика 191 | * @returns {Boolean} 192 | */ 193 | FlashInterface.prototype.isPreloaded = function(id, src, offset) { 194 | return this._callFlash("isPreloaded", id, src, offset == null ? 1 : offset); 195 | }; 196 | 197 | /** 198 | * Проверить что трек начал предзагружаться 199 | * @param {int} id - id плеера 200 | * @param {String} src - ссылка на трек 201 | * @param {int} [offset=1] - 0: для текущего загрузчика, 1: для следующего загрузчика 202 | * @returns {Boolean} 203 | */ 204 | FlashInterface.prototype.isPreloading = function(id, src, offset) { 205 | return this._callFlash("isPreloading", id, src, offset == null ? 1 : offset); 206 | }; 207 | 208 | /** 209 | * Запустить воспроизведение предзагруженного трека 210 | * @param {int} id - id плеера 211 | * @param {int} [offset=1] - 0: текущий загрузчик, 1: следующий загрузчик 212 | * @returns {Boolean} -- доступность данного действия 213 | */ 214 | FlashInterface.prototype.playPreloaded = function(id, offset) { 215 | return this._callFlash("playPreloaded", id, offset == null ? 1 : offset); 216 | }; 217 | 218 | // ================================================================= 219 | 220 | // Получение данных о плеере 221 | 222 | // ================================================================= 223 | 224 | /** 225 | * Получить ссылку на трек 226 | * @param {int} id - id плеера 227 | * @param {int} [offset=0] - 0: текущий загрузчик, 1: следующий загрузчик 228 | * @returns {String} 229 | */ 230 | FlashInterface.prototype.getSrc = function(id, offset) { 231 | return this._callFlash("getSrc", id, offset || 0); 232 | }; 233 | 234 | /** 235 | * Проверить доступность воcпроизведения без пользовательского взаимодействия 236 | * @returns {boolean} 237 | */ 238 | FlashInterface.prototype.isAutoplayable = function() { 239 | return true; 240 | }; 241 | 242 | module.exports = FlashInterface; 243 | -------------------------------------------------------------------------------- /tutorial/fx.md: -------------------------------------------------------------------------------- 1 | Обработка звука 2 | =============== 3 | 4 | В состав данной библиотеки входят 2 подмодуля: 5 | 6 | - эквалайзер; 7 | - набор функций для работы с разными единицами и шкалами для измерения громкости. 8 | 9 | Оба подмодуля несут исключительно вспомогательную функцию, для работы самой библиотеки их использование не требуется. Также эти модули можно применять не только к самому плееру. 10 | 11 | Web Audio API 12 | ------------- 13 | 14 | Стоит сразу оговориться, что эквалайзер - это технология Web Audio API и нужно понимать то, как данная библиотека работает с этим самым API. Есть несколько особенностей, которые стоит учитывать проектируя собственные фильтры препроцессора (да и просто для использования эквалайзера не плохо знать это). 15 | 16 | 1. При использовании YandexAudio API не требуется вручную создавать контекст AudioContext. Этот объект создается библиотекой YandexAudio автоматически, после ее загрузки на страницу (если есть поддержка технологии Web Audio API в браузере). Созданный аудио-контекст доступен через `ya.music.Audio.audioContext`. 17 | 18 | 2. Web Audio API более требователен к контенту - в случае если аудио-данные берутся со стороннего домена, требуется чтобы был включен режим CORS и целевой домен проставлял корректные заголовки `Access-Control-Allow-Origin`. Для включения данного режима используется метод `YadexAudio#toggleCrossDomain(true)`. **Важно** - если сервер не отправляет необходимые заголовки включение данного режима не позволит загрузить трек даже если не используется Web Audio API, так что не стоит включать этот режим превентивно, если не планируется использование Web Audio API. 19 | 20 | 3. Web Audio API не умеет напрямую использовать элементы `audio` в графе. Для их использования требуется создать элемент `MediaElementAudioSourceNode`, указав `audio` элемент как источник звука (опять же, подробнее в статье [Web Audio API](web-audio-api.md) в разделе **Источники сигнала**). Далее, так как мы хотим работать с изначальным сигналом без всяких искажений (включая изменение громкости, которое очень критично для некоторых фильтров), нам потребуется создать элемент `GainNode`, который будет управлять итоговой громкостью, а самому элементу `audio` выставить максимальную громкость. Именно это проделывает вызов `YandexAudio#toggleWebAudioAPI(true)` - создает необходимое окружение для использования Web Audio API. 21 | 22 | 23 | 4. **Важно**. Однажды включенный режим Web Audio API отключить нельзя! Когда вызывается метод `YandexAudio#toggleWebAudioAPI(false)` граф просто максимально сокращается. Из него удаляется препроцессор, управление громкостью при этом по-прежнему ведется через `GainNode`, так как некоторые браузеры не возвращают корректно управление громкостью элементу `audio`. 24 | 25 | Сам процесс подключения различных препроцессоров и фильтров предельно прост. Препроцессор описывается двумя нодами - входной и выходной (для простых фильтров это может быть одна и та же нода). Выход `MediaElementAudioSourceNode` подключается ко входу входной ноды, а выход выходной ноды препроцессора подключается к входу `GainNode`. Таким образом, если требуется подключить более одного фильтра, то достаточно соединить их в нужной последовательности и передать в качестве входной и выходной ноды соответствующие ноды этой конструкции. 26 | 27 | ```(javascript) 28 | equalizer.output.connect(convolverNode); 29 | convolverNode.connect(dynamicsCompressorNode); 30 | dynamicsCompressorNode.connect(analyzerNode); 31 | 32 | player.setAudioPreprocessor({ 33 | input: equalizer.input, 34 | output: dynamicsCompressorNode // нам не нужен выход analyzerNode, и ее не обязательно подключать к какому-либо выходу 35 | }); 36 | ``` 37 | 38 | 39 | 40 | 41 | Эквалайзер 42 | ---------- 43 | 44 | Эквалайзер - это инструмент для фильтрации звука, позволяющий настраивать различную громкость для разных полос пропускания. Он используется преимущественно для компенсации недостатков записывающей и/или воспроизводящей аппаратуры, но может применяться и для придания большей выразительности звучанию. 45 | 46 | Стоит сразу упомянуть, что эквалайзер реализован только для html5-версии плеера, т.к. он использует технологию [Web Audio API](web-audio-api.md). Рассмотрим небольшой пример того, как подключить и использовать эквалайзер: 47 | 48 | ```(javascript) 49 | // Сахар для удобства доступа 50 | var YandexAudio = ya.music.Audio; 51 | var Equalizer = YandexAudio.fx.Equalizer; 52 | 53 | // При создании плеера непосредственно указываем html5, т.к. эквалайзер не будет работать с flash-плеером 54 | var player = new YandexAudio("html5"); 55 | var equalizer = null; 56 | 57 | // Дожидаемся завершения инициализации плеера 58 | player.initPromise().then(function() { 59 | if (!player.toggleWebAudioAPI(true)) { 60 | // Если не удается включить Web Audio API, значит либо запустился flash-плеер, либо нет поддержки Web Audio API 61 | console.warn("Эквалайзер недоступен"); 62 | } else { 63 | // Создаем эквалайзер со стандартным набором полос пропускания 64 | equalizer = new Equalizer(YandexAudio.audioContext, Equalizer.DEFAULT_BANDS); 65 | 66 | // Находим нужный пресет из списка стандартных 67 | for (var i = 0, l = Equalizer.DEFAULT_PRESETS.length; i < l; i++) { 68 | if (Equalizer.DEFAULT_PRESETS[i].id === "Full Bass & Treble") { 69 | var preset = Equalizer.DEFAULT_PRESETS[i]; 70 | break; 71 | } 72 | } 73 | 74 | // Загружаем пресет в эквалайзер 75 | equalizer.loadPreset(preset); 76 | 77 | // Подключаем эквалайзер к плееру 78 | player.setAudioPreprocessor(equalizer); 79 | } 80 | }); 81 | ``` 82 | 83 | Тут стоит пояснить некоторые вещи. 84 | 85 | Полосой пропускания называется диапазон частот, к которому применяется некий фильтр. В нашем случае этот фильтр реализован с помощью `BiquadFilterNode`, для первой и последней полосы он имеет тип `lowshelf` и `highshelf` соответственно, а для всех остальных - `peaking`. Подробнее про это можно почитать в статье [Web Audio API](web-audio-api.md) в разделе **BiquadFilterNode**. Фильтры для каждой полосы соединены последовательно начиная с самой маленькой частоты и заканчивая самой высокой. В данном примере используется стандартный набор из 10 полос с частотами `[60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000]` (значения указаны в герцах). 86 | 87 | Пресеты эквалайзера - это набор настроек усиления для каждой полосы пропускания, плюс значение предусиления. Предусиление выбирается таким образом, чтобы после применения эквалайзера общая громкость звука сохранялась неизменной. Альтернативный подход - использовать предусиление, с отрицательным значением равным максимальному усилению среди всех полос, чтобы гарантированно избежать клиппинга сигнала в результате последующей обработки (однако, в данном случае общая громкость будет ниже, чем у входного сигнала). 88 | 89 | 90 | 91 | Громкость 92 | --------- 93 | 94 | С данным подмодулем все намного проще - это всего лишь набор формул для перевода следующих величин: 95 | 96 | - **dBFS** - полный динамический диапазон. Шкала в децибелах от минус бесконечности до нуля (подробнее в статье про [теорию звука](sound.md) в разделе **Уровень сигнала**), 97 | - экспоненциальная шкала - шкала относительной громкости (от 0 до 1) с экспоненциальным шагом. Позволяет более точно регулировать громкость вблизи нижней границы и делает более существенные изменения громкости вблизи верхней границы. 98 | 99 | Оригинальная шкала громкости, является линейной шкалой относительной громкости (от 0 до 1). Все методы данного подмодуля переводят значения из этой шкалы или в эту шкалу. 100 | -------------------------------------------------------------------------------- /jsdoc/doc/publish.js: -------------------------------------------------------------------------------- 1 | var util = require("util"); 2 | var render = require('./render'); 3 | var fs = require("fs"); 4 | 5 | var makeKinds = function() { 6 | return { 7 | "class": [], 8 | "typedef": [], 9 | "namespace": [], 10 | 11 | "event": [], 12 | 13 | "function": [], 14 | "member": [], 15 | 16 | // "file": [], 17 | "package": [] 18 | }; 19 | }; 20 | 21 | var checkDoc = function(data, includePrivate) { 22 | return (includePrivate || !data.access || data.access == "public") 23 | && (!data.undocumented) 24 | && (!data.ignore); 25 | }; 26 | 27 | var optimize = function(tree, indent) { 28 | indent = indent || 0; 29 | var orig = Object.keys(tree.sub); 30 | var key, keys; 31 | 32 | for (var k = 0; k < orig.length; k++) { 33 | key = orig[k]; 34 | keys = Object.keys(tree.sub[key].sub); 35 | 36 | if (keys.length == 1 && !tree.sub[key].sub[keys[0]].link) { 37 | tree.sub[key + "." + keys[0]] = tree.sub[key].sub[keys[0]]; 38 | delete tree.sub[key]; 39 | orig.push(key + "." + keys[0]); 40 | } else { 41 | optimize(tree.sub[key], indent + 1); 42 | } 43 | } 44 | tree.indent = new Array(indent + 1).join(" "); 45 | }; 46 | var makePath = function(path, name) { 47 | path = path.split("."); 48 | // var symbol = path.pop(); 49 | var ns = docs.exportTree; 50 | var full = ""; 51 | 52 | path.forEach(function(chunk) { 53 | full += (full ? "." : "") + chunk; 54 | if (!ns.sub[chunk]) { 55 | ns.sub[chunk] = { 56 | sub: {}, 57 | indent: 0, 58 | full: full, 59 | link: null 60 | }; 61 | } 62 | ns = ns.sub[chunk]; 63 | }); 64 | 65 | ns.link = name; 66 | }; 67 | 68 | var weight = function(data) { 69 | return (data.kind != "class" && data.static ? 10 : 0) 70 | + (data.inner ? -1 : 0) 71 | + (data.exported ? 5 : 0); 72 | }; 73 | var sort = function(kinds) { 74 | for (var kind in kinds) { 75 | if (Array.isArray(kinds[kind])) { 76 | kinds[kind] = kinds[kind].sort(function(a, b) { 77 | return weight(b) - weight(a); 78 | }); 79 | } 80 | } 81 | }; 82 | 83 | var fixParams = function(params) { 84 | params.forEach(function(param) { 85 | param.description = param.description && param.description.replace(/\n/g, " "); 86 | 87 | param.type && param.type.names && (param.type.names = param.type.names.map(function(type) { 88 | type = type 89 | .replace(/[\w.#~]*~/, "") 90 | .replace(/\(|\)/g, ""); 91 | 92 | return type; 93 | })); 94 | }); 95 | return params; 96 | }; 97 | var fixes = function(data) { 98 | data.type && fixParams([data]); 99 | data.params && fixParams(data.params); 100 | data.properties && fixParams(data.properties); 101 | data.returns && fixParams(data.returns) && (data.returns = data.returns[0]); 102 | 103 | if (data.kind === 'constant') { 104 | data.kind = 'member'; 105 | data.const = true; 106 | } 107 | if (data.kind === 'namespace') { 108 | data.namespace = true; 109 | } 110 | 111 | if (data.kind === 'class') { 112 | data["class"] = true; 113 | } 114 | 115 | if (data.scope === 'static') { 116 | data.static = true; 117 | } else if (data.scope === 'inner') { 118 | data.inner = true; 119 | } 120 | 121 | data.fires && (data.fires = data.fires.map(function(event) { return event.replace("event:", ""); })); 122 | if (data.kind === 'event') { data.longname = data.longname.replace("event:", ""); } 123 | 124 | data.tags && data.tags.forEach(function(tag) { 125 | if (tag.title === "exported") { 126 | data.exported = tag.value; 127 | makePath(tag.value, data.name); 128 | } 129 | if (tag.title === "unignore") { 130 | data.unignore = true; 131 | } 132 | }); 133 | }; 134 | 135 | var subtree = function(taffy, data) { 136 | data.children = makeKinds(); 137 | 138 | taffy({memberof: data.longname}).each(function(member) { 139 | if (!checkDoc(member)) { 140 | return; 141 | } 142 | 143 | if (data.children[member.kind]) { 144 | data.children[member.kind].push(member); 145 | } else { 146 | console.warn("Unexpected type", member.kind); 147 | return; 148 | } 149 | 150 | member.parent = data; 151 | subtree(taffy, member); 152 | }); 153 | 154 | delete data.children.package; 155 | sort(data.children); 156 | }; 157 | 158 | var docs = { 159 | exportTree: { 160 | sub: {}, 161 | symbols: [] 162 | }, 163 | links: {}, 164 | tree: makeKinds(), 165 | linear: makeKinds() 166 | }; 167 | 168 | var pendingExported = {}; 169 | var unignore = {}; 170 | 171 | var prepare = function(taffy, style) { 172 | taffy().each(function(data) { 173 | fixes(data); 174 | 175 | if (unignore[data.longname]) { 176 | data.ignore = false; 177 | } 178 | 179 | if (!checkDoc(data)) { 180 | if (data.exported) { 181 | pendingExported[data.longname] = data.exported; 182 | } 183 | if (data.unignore) { 184 | unignore[data.longname] = true; 185 | } 186 | 187 | return; 188 | } 189 | 190 | if (pendingExported[data.longname]) { 191 | data.exported = pendingExported[data.longname]; 192 | } 193 | 194 | if (data.kind === 'function' && !data.description) { 195 | // console.log(util.inspect(data, {color: true, depth: 0})); 196 | } 197 | 198 | if (!docs.linear[data.kind]) { 199 | console.warn("Unexpected kind", data.kind); 200 | return; 201 | } 202 | 203 | var link = (data.memberof && !data.inner ? data.memberof + ( 204 | data.static ? "." : "#") 205 | : "" 206 | ) + data.name; 207 | 208 | docs.links[link] = data; 209 | 210 | if (data.scope === 'global' 211 | || data.kind === 'class' 212 | || data.kind === 'typedef' 213 | || data.kind === 'namespace') { 214 | docs.linear[data.kind].push(data); 215 | } 216 | }); 217 | 218 | taffy({scope: "global"}).each(function(data) { 219 | if (!checkDoc(data)) { 220 | return; 221 | } 222 | 223 | if (docs.tree[data.kind]) { 224 | docs.tree[data.kind].push(data); 225 | subtree(taffy, data); 226 | } 227 | }); 228 | 229 | delete docs.tree.package; 230 | delete docs.linear.package; 231 | 232 | sort(docs.tree); 233 | sort(docs.linear); 234 | 235 | if (!/jsdoc/.test(style)) { 236 | optimize(docs.exportTree); 237 | } 238 | }; 239 | 240 | var styles = { 241 | "jsdoc": "jsdoc", 242 | "jsdoc-tech": "jsdoc", 243 | "gfm-single": "md", 244 | "gfm-files": "md" 245 | }; 246 | 247 | exports.publish = function(taffyData, opts, tutorials) { 248 | var style = opts.query && opts.query.style || "gfm-single"; 249 | var out = opts.query && opts.query.out || "readme"; 250 | 251 | prepare(taffyData, style); 252 | 253 | render.prepare(opts.template, style); 254 | var files = render.render(docs, out); 255 | 256 | var ext = "." + styles[style]; 257 | 258 | try { 259 | fs.mkdirSync(opts.destination); 260 | } catch(e) {} 261 | 262 | for (var name in files) { 263 | fs.writeFileSync(opts.destination + name.replace(/~/g, "-") + ext, files[name]); 264 | } 265 | 266 | // console.log(util.inspect(docs.linear["class"], {color: true, depth: 2})); 267 | }; 268 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | YandexAudio 2 | =========== 3 | YandexAudio — JavaScript-библиотека, предназначенная для воспроизведения аудиоданных в браузере. Работа YandexAudio базируется на технологиях html5 audio и flash. Библиотека предоставляет удобный интерфейс для добавления этих технологий на страницу и управления их настройками. При этом YandexAudio решает основные проблемы, связанные с использованием audio и flash – неполная поддержка браузерами, трудная отладка ошибок и др. 4 | 5 | Основные возможности YandexAudio: 6 | - автоматическое определение поддерживаемых браузером технологий; 7 | - подключение flash-плеера на страницу с возможностью отображения видимого flash-апплета; 8 | - автоматическая перезагрузка плеера при возникновении ошибки (например из-за заблокированного flash-содержимого); 9 | - предзагрузка следующего трека, параллельно с воспроизведением текущего; 10 | - детектирование и использование технологий Web Audio API; 11 | - эквалайзер (с готовым набором пресетов) с возможностью настройки количества и частоты полос пропускания. 12 | 13 | 14 | 15 | Подключение 16 | ---------- 17 | Существует 2 способа подключения данной библиотеки: 18 | 19 | - **npm** - если ваш проект использует сборку скриптов с помощью browserify или аналога, то можно просто подключать 20 | библиотку как npm-пакет `var YandexAudio = require('YandexAudio')`; 21 | - **скрипт** - достаточно подключить основной файл скрипта 22 | ([dist/index.js](https://music.yandex.ru/api/audio/dist/index.js) 23 | или [dist/index.min.js](https://music.yandex.ru/api/audio/dist/index.min.js) - минифицированную версию) 24 | в тело страницы и далее использовать глобально доступный объект [spec/Audio.md](ya.music.Audio). 25 | 26 | 27 | Использование 28 | ------------ 29 | ### Создание экземпляра плеера: 30 | 31 | ```javascript 32 | var audioPlayer = new ya.music.Audio(preferredPlayerType, flashOverlayElement); 33 | audioPlayer.initPromise().then(function() { 34 | console.log("Аудио-плеер готов к работе"); 35 | }, function() { 36 | console.error("Не удалось инициализировать аудио-плеер"); 37 | }); 38 | ``` 39 | 40 | - **preferredPlayerType** - предпочитаемый тип плеера. Может принимать значения: `"html5"`, `"flash"` или любое ложное значение (false, null, undefined, 0, ""). Если выбранный тип плеера окажется недоступен, будет запущен оставшийся тип. Если параметр не передан, либо указано ложное значение, то API автоматически выберет поддерживаемый тип плеера. 41 | - **flashOverlayElement** - HTMLElement, в который требуется встроить flash-апплет. Передается в том случае, если необходимо отобразить видимый flash-апплет для отключения различных блокировщиков flash'а. 42 | 43 | ### Запуск воспроизведения 44 | 45 | ```javascript 46 | audioPlayer.play(src).then(function() { 47 | console.log("Воспроизведение успешно началось"); 48 | }, function(err) { 49 | console.error("Не удалось начать воспроизведенние", err); 50 | }); 51 | ``` 52 | 53 | ### Управление воспроизведением 54 | 55 | ```javascript 56 | audioPlayer.pause(); // пауза 57 | audioPlayer.resume(); // продолжение воспроизведения 58 | audioPlayer.stop(); // остановка воспроизведения и загрузки трека 59 | 60 | console.log("Ссылка на текущий трек", audioPlayer.getSrc()); 61 | console.log("Длительность трека", audioPlayer.getDuration()); 62 | console.log("Текущая позиция воспроизведения", audioPlayer.getPosition()); 63 | console.log("Длительность загруженной части", audioPlayer.getLoaded()); 64 | console.log("Время воспроизведения трека", audioPlayer.getPlayed()); 65 | 66 | console.log("Новая позиция воспроизведения", audioPlayer.setPosition(position)); 67 | ``` 68 | 69 | ### Прослушивание событий 70 | 71 | ```javascript 72 | audioPlayer.on(ya.music.Audio.EVENT_STATE, function(state) { 73 | switch(state) { 74 | case ya.music.Audio.STATE_INIT: console.log("Инициализация плеера"); break; 75 | case ya.music.Audio.STATE_IDLE: console.log("Плеер готов и ожидает"); break; 76 | case ya.music.Audio.STATE_PLAYING: console.log("Плеер проигрывает музыку"); break; 77 | case ya.music.Audio.STATE_PAUSED: console.log("Плеер поставлен на паузу"); break; 78 | case ya.music.Audio.STATE_CRASHED: console.log("Не удалось инициализировать плеер"); break; 79 | } 80 | }); 81 | 82 | var logEvent = function(text) { return function(data) { console.log(text, data); }; }; 83 | audioPlayer.on(ya.music.Audio.EVENT_PLAY, logEvent("Плеер начал воспроизведение трека")); 84 | audioPlayer.on(ya.music.Audio.EVENT_STOP, logEvent("Остановка воспроизведения")); 85 | audioPlayer.on(ya.music.Audio.EVENT_PAUSE, logEvent("Пауза воспроизведения")); 86 | 87 | audioPlayer.on(ya.music.Audio.EVENT_PROGRESS, logEvent("Обновление позиции воспроизведения")); 88 | audioPlayer.on(ya.music.Audio.EVENT_ENDED, logEvent("Воспроизведение трека завершено")); 89 | 90 | audioPlayer.on(ya.music.Audio.EVENT_LOADING, logEvent("Трек начал загружаться")); 91 | audioPlayer.on(ya.music.Audio.EVENT_LOADED, logEvent("Трек загружен полностью")); 92 | 93 | audioPlayer.on(ya.music.Audio.EVENT_VOLUME, logEvent("Изменение громкости")); 94 | 95 | audioPlayer.on(ya.music.Audio.EVENT_ERROR, logEvent("Возникла ошибка при воспроизведении")); 96 | audioPlayer.on(ya.music.Audio.EVENT_CRASHED, logEvent("Крах инициализации")); 97 | 98 | audioPlayer.on(ya.music.Audio.EVENT_SWAP, logEvent("Переключение между текущим и предзагруженным треком")); 99 | ``` 100 | 101 | ### Прелоадер 102 | В большинство команд управления можно передать вторым аргументом `1`, чтобы они применялись к прелоадеру вместо текущего плеера. 103 | Для прослушивания событий плероадера следует использовать префикс `ya.music.Audio.PRELOADER_EVENT`. 104 | 105 | ```javascript 106 | // Следует обратить внимание, что обещание разрешится, когда трек начал загружаться, а не когда загрузился 107 | audioPlayer.preload(src).then(function() { 108 | console.log("Началась предзагрузка трека"); 109 | }, function(err) { 110 | console.error("Ну удалось начать загрузку трека", err); 111 | }); 112 | 113 | audioPlayer.playPreloaded().then(function() { 114 | console.log("Воспроизведение успешно началось"); 115 | }, function(err) { 116 | console.error("Не удалось начать воспроизведенние", err); 117 | }); 118 | 119 | audioPlayer.stop(1); // остановка загрузки трека 120 | 121 | console.log("Ссылка на текущий трек", audioPlayer.getSrc(1)); 122 | console.log("Длительность трека", audioPlayer.getDuration(1)); 123 | console.log("Длительность загруженной части", audioPlayer.getLoaded(1)); 124 | 125 | // Два похожих метода, но первый является сахаром для audioPlayer.getSrc(1) == src, а второй проверяет успех начала загрузки 126 | console.log("Трек " + (audioPlayer.isPreloading(src) ? "загружается/ожидает загрузки" : "не загружается")); 127 | console.log("Трек " + (audioPlayer.isPreloaded(src) ? "начал загружаться" : "не загружается")); 128 | 129 | var logEvent = function(text) { return function(data) { console.log(text, data); }; }; 130 | audioPlayer.on(ya.music.Audio.PRELOADER_EVENT + ya.music.Audio.EVENT_STOP, logEvent("Остановка загрузки")); 131 | 132 | audioPlayer.on(ya.music.Audio.PRELOADER_EVENT + ya.music.Audio.EVENT_PROGRESS, logEvent("Процесс загрузки")); 133 | 134 | audioPlayer.on(ya.music.Audio.PRELOADER_EVENT + ya.music.Audio.EVENT_LOADING, logEvent("Трек начал загружаться")); 135 | audioPlayer.on(ya.music.Audio.PRELOADER_EVENT + ya.music.Audio.EVENT_LOADED, logEvent("Трек загружен полностью")); 136 | 137 | audioPlayer.on(ya.music.Audio.PRELOADER_EVENT + ya.music.Audio.EVENT_ERROR, logEvent("Возникла ошибка при загрузке")); 138 | ``` 139 | 140 | ### Документация 141 | * [Справочник API](spec) 142 | * [Быстрый старт](tutorial/quick-start.md) 143 | * [Обработка звука](tutorial/fx.md) 144 | * [Подводные камни](tutorial/corner-case.md) 145 | * [Полезная теоретическая информация](tutorial/sound.md) 146 | * [Web Audio API](tutorial/web-audio-api.md) 147 | * [Инструкции для контрибьюторов](tutorial/contrib.md) 148 | --------------------------------------------------------------------------------