pageflow 13.0.0.beta6 → 13.0.0.beta7

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of pageflow might be problematic. Click here for more details.

Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -0
  3. data/README.md +1 -0
  4. data/app/assets/audios/pageflow/unmute.mp3 +0 -0
  5. data/app/assets/images/pageflow/editor/help/phone_horizontal_slideshow_mode.gif +0 -0
  6. data/app/assets/javascripts/pageflow/asset_urls.js.erb +1 -1
  7. data/app/assets/javascripts/pageflow/audio/multi_player.js +4 -0
  8. data/app/assets/javascripts/pageflow/audio_player.js +1 -1
  9. data/app/assets/javascripts/pageflow/background_media.js +22 -0
  10. data/app/assets/javascripts/pageflow/base.js +1 -0
  11. data/app/assets/javascripts/pageflow/browser/agent.js +92 -78
  12. data/app/assets/javascripts/pageflow/browser/autoplay_support.js +2 -2
  13. data/app/assets/javascripts/pageflow/dist/react.js +711 -252
  14. data/app/assets/javascripts/pageflow/editor/api/page_type.js +4 -0
  15. data/app/assets/javascripts/pageflow/editor/base.js +1 -0
  16. data/app/assets/javascripts/pageflow/editor/collections/files_collection.js +8 -0
  17. data/app/assets/javascripts/pageflow/editor/initializers/boot.js +6 -4
  18. data/app/assets/javascripts/pageflow/editor/initializers/setup_asset_urls.js +3 -0
  19. data/app/assets/javascripts/pageflow/editor/models/entry.js +2 -2
  20. data/app/assets/javascripts/pageflow/editor/models/file_stage.js +6 -1
  21. data/app/assets/javascripts/pageflow/editor/models/mixins/stage_provider.js +9 -0
  22. data/app/assets/javascripts/pageflow/editor/models/mixins/transient_references.js +12 -2
  23. data/app/assets/javascripts/pageflow/editor/models/preview_entry_data.js +5 -0
  24. data/app/assets/javascripts/pageflow/editor/models/uploaded_file.js +6 -1
  25. data/app/assets/javascripts/pageflow/editor/templates/emulation_mode_button.jst.ejs +26 -0
  26. data/app/assets/javascripts/pageflow/editor/templates/entry_preview.jst.ejs +8 -3
  27. data/app/assets/javascripts/pageflow/editor/views/configuration_editors/groups/options.js +1 -1
  28. data/app/assets/javascripts/pageflow/editor/views/configuration_editors/groups/page_link.js +3 -6
  29. data/app/assets/javascripts/pageflow/editor/views/configuration_editors/groups/page_transitions.js +14 -4
  30. data/app/assets/javascripts/pageflow/editor/views/edit_storyline_view.js +3 -6
  31. data/app/assets/javascripts/pageflow/editor/views/embedded/background_image_embedded_view.js +47 -12
  32. data/app/assets/javascripts/pageflow/editor/views/emulation_mode_button_view.js +45 -0
  33. data/app/assets/javascripts/pageflow/editor/views/entry_preview_view.js +62 -8
  34. data/app/assets/javascripts/pageflow/editor/views/file_stage_item_view.js +7 -0
  35. data/app/assets/javascripts/pageflow/editor/views/help_image_view.js +9 -0
  36. data/app/assets/javascripts/pageflow/editor/views/info_box_view.js +1 -1
  37. data/app/assets/javascripts/pageflow/editor/views/inputs/file_processing_state_display_view.js +60 -0
  38. data/app/assets/javascripts/pageflow/editor/views/sidebar_footer_view.js +12 -0
  39. data/app/assets/javascripts/pageflow/editor/views/widget_types/phone_horizontal_slideshow_mode.js +14 -0
  40. data/app/assets/javascripts/pageflow/entry_data.js +4 -0
  41. data/app/assets/javascripts/pageflow/media_player.js +7 -3
  42. data/app/assets/javascripts/pageflow/media_player/handle_failed_play.js +34 -0
  43. data/app/assets/javascripts/pageflow/media_player/volume_fading/web_audio.js +29 -3
  44. data/app/assets/javascripts/pageflow/page_transitions.js +59 -15
  45. data/app/assets/javascripts/pageflow/page_type.js +5 -1
  46. data/app/assets/javascripts/pageflow/seed_entry_data.js +13 -0
  47. data/app/assets/javascripts/pageflow/slideshow.js +31 -11
  48. data/app/assets/javascripts/pageflow/slideshow/atmo.js +23 -12
  49. data/app/assets/javascripts/pageflow/slideshow/lazy_page_widget.js +9 -3
  50. data/app/assets/javascripts/pageflow/slideshow/navigation_direction.js +37 -0
  51. data/app/assets/javascripts/pageflow/slideshow/page_widget.js +12 -6
  52. data/app/assets/javascripts/pageflow/slideshow/scroll_indicator_widget.js +13 -16
  53. data/app/assets/javascripts/pageflow/slideshow/scroller_widget.js +49 -14
  54. data/app/assets/javascripts/pageflow/ui/views/mixins/input_view.js +10 -3
  55. data/app/assets/javascripts/pageflow/video_player/lazy.js +1 -1
  56. data/app/assets/stylesheets/pageflow/animations/bounce.scss +13 -1
  57. data/app/assets/stylesheets/pageflow/editor/base.scss +5 -0
  58. data/app/assets/stylesheets/pageflow/editor/emulation_mode_button.scss +78 -0
  59. data/app/assets/stylesheets/pageflow/editor/entry_preview.scss +41 -0
  60. data/app/assets/stylesheets/pageflow/editor/file_stages.scss +11 -3
  61. data/app/assets/stylesheets/pageflow/editor/help.scss +4 -14
  62. data/app/assets/stylesheets/pageflow/editor/help_image.scss +5 -0
  63. data/app/assets/stylesheets/pageflow/editor/info_box.scss +5 -0
  64. data/app/assets/stylesheets/pageflow/editor/inputs.scss +1 -0
  65. data/app/assets/stylesheets/pageflow/editor/inputs/file_processing_state_display.scss +18 -0
  66. data/app/assets/stylesheets/pageflow/editor/sidebar_footer.scss +12 -0
  67. data/app/assets/stylesheets/pageflow/entries.scss +29 -6
  68. data/app/assets/stylesheets/pageflow/mixins/breakpoints.scss +5 -3
  69. data/app/assets/stylesheets/pageflow/mixins/icons/fontawesome.scss +1 -1
  70. data/app/assets/stylesheets/pageflow/navigation_mobile.scss +5 -1
  71. data/app/assets/stylesheets/pageflow/page_transitions.scss +0 -6
  72. data/app/assets/stylesheets/pageflow/page_transitions/crossfade.scss +5 -1
  73. data/app/assets/stylesheets/pageflow/page_transitions/fade.scss +44 -36
  74. data/app/assets/stylesheets/pageflow/page_transitions/scroll.scss +96 -11
  75. data/app/assets/stylesheets/pageflow/slideshow.scss +0 -4
  76. data/app/assets/stylesheets/pageflow/themes/default/background_media_unmute_button.scss +68 -0
  77. data/app/assets/stylesheets/pageflow/themes/default/base.scss +1 -0
  78. data/app/assets/stylesheets/pageflow/themes/default/indicators/icons/icon_font.scss +7 -0
  79. data/app/assets/stylesheets/pageflow/ui/forms.scss +4 -0
  80. data/app/controllers/pageflow/editor/files_controller.rb +9 -1
  81. data/app/helpers/pageflow/asset_urls_helper.rb +9 -0
  82. data/app/helpers/pageflow/background_image_helper.rb +6 -10
  83. data/app/helpers/pageflow/file_background_images_helper.rb +78 -0
  84. data/app/helpers/pageflow/pages_helper.rb +2 -2
  85. data/app/helpers/pageflow/render_json_helper.rb +3 -2
  86. data/app/models/concerns/pageflow/hosted_file.rb +2 -9
  87. data/app/models/concerns/pageflow/uploaded_file.rb +9 -0
  88. data/app/models/pageflow/draft_entry.rb +2 -2
  89. data/app/models/pageflow/image_file.rb +5 -20
  90. data/app/models/pageflow/image_file_css_background_image_urls.rb +17 -0
  91. data/app/models/pageflow/video_file_css_background_image_urls.rb +13 -0
  92. data/app/views/pageflow/editor/asset_urls/_asset_urls.json.jbuilder +4 -0
  93. data/app/views/pageflow/editor/entries/seed.json.erb +3 -1
  94. data/app/views/pageflow/editor/image_files/_image_file.json.jbuilder +1 -1
  95. data/app/views/pageflow/entries/_indicators.html.erb +8 -3
  96. data/app/views/pageflow/entries/edit.html.erb +1 -1
  97. data/app/views/pageflow/entries/show.css.erb +6 -20
  98. data/app/views/pageflow/file_background_images/_rule.css.erb +3 -0
  99. data/config/initializers/features.rb +1 -0
  100. data/config/locales/de.yml +20 -8
  101. data/config/locales/en.yml +23 -11
  102. data/config/routes.rb +1 -0
  103. data/lib/pageflow/built_in_file_type.rb +4 -0
  104. data/lib/pageflow/built_in_widget_type.rb +12 -0
  105. data/lib/pageflow/built_in_widget_types_plugin.rb +5 -0
  106. data/lib/pageflow/file_type.rb +39 -0
  107. data/lib/pageflow/file_types.rb +6 -0
  108. data/lib/pageflow/version.rb +1 -1
  109. data/spec/factories/hosted_files.rb +12 -8
  110. data/vendor/assets/javascripts/audio5.min.js +280 -129
  111. data/vendor/assets/javascripts/iscroll.js +16 -11
  112. metadata +47 -16
  113. data/app/assets/javascripts/pageflow/media_player/catch_play_promise.js +0 -23
  114. data/app/assets/stylesheets/pageflow/page_transitions/scroll_in.scss +0 -66
  115. data/app/assets/stylesheets/pageflow/page_transitions/scroll_in_right.scss +0 -68
  116. data/app/assets/stylesheets/pageflow/page_transitions/scroll_left.scss +0 -20
  117. data/app/assets/stylesheets/pageflow/page_transitions/scroll_over_from_left.scss +0 -12
  118. data/app/assets/stylesheets/pageflow/page_transitions/scroll_over_from_right.scss +0 -12
  119. data/app/assets/stylesheets/pageflow/page_transitions/scroll_right.scss +0 -20
  120. data/lib/pageflow/images/palette.png +0 -0
@@ -8,6 +8,8 @@ module Pageflow
8
8
  editor_partial: 'pageflow/editor/image_files/image_file',
9
9
  collection_name: 'image_files',
10
10
  url_templates: ImageFileUrlTemplates.new,
11
+ css_background_image_urls: ImageFileCssBackgroundImageUrls.new,
12
+ css_background_image_class_prefix: 'image',
11
13
  top_level_type: true)
12
14
  end
13
15
 
@@ -17,6 +19,8 @@ module Pageflow
17
19
  editor_partial: 'pageflow/editor/video_files/video_file',
18
20
  collection_name: 'video_files',
19
21
  url_templates: VideoFileUrlTemplates.new,
22
+ css_background_image_urls: VideoFileCssBackgroundImageUrls.new,
23
+ css_background_image_class_prefix: 'video_poster',
20
24
  top_level_type: true,
21
25
  nested_file_types: [BuiltInFileType.text_track])
22
26
  end
@@ -32,8 +32,20 @@ module Pageflow
32
32
  new('slim_player_controls', ['player_controls'], 'pageflow/widgets/placeholder')
33
33
  end
34
34
 
35
+ def self.default_slideshow_mode
36
+ new('default_slideshow_mode', ['slideshow_mode'], 'pageflow/widgets/placeholder')
37
+ end
38
+
39
+ def self.phone_horizontal_slideshow_mode
40
+ new('phone_horizontal_slideshow_mode', ['slideshow_mode'], 'pageflow/widgets/placeholder')
41
+ end
42
+
35
43
  def self.cookie_notice_bar
36
44
  Pageflow::React.create_widget_type('cookie_notice_bar', 'cookie_notice')
37
45
  end
46
+
47
+ def self.unmute_button
48
+ Pageflow::React.create_widget_type('unmute_button', 'background_media_control')
49
+ end
38
50
  end
39
51
  end
@@ -1,11 +1,16 @@
1
1
  module Pageflow
2
2
  class BuiltInWidgetTypesPlugin < Plugin
3
3
  def configure(config)
4
+ config.widget_types.register(Pageflow::BuiltInWidgetType.default_slideshow_mode,
5
+ default: true)
6
+ config.widget_types.register(Pageflow::BuiltInWidgetType.phone_horizontal_slideshow_mode)
7
+
4
8
  config.widget_types.register(Pageflow::BuiltInWidgetType.navigation, default: true)
5
9
  config.widget_types.register(Pageflow::BuiltInWidgetType.mobile_navigation, default: true)
6
10
  config.widget_types.register(Pageflow::BuiltInWidgetType.slim_player_controls)
7
11
  config.widget_types.register(Pageflow::BuiltInWidgetType.classic_player_controls, default: true)
8
12
  config.widget_types.register(Pageflow::BuiltInWidgetType.cookie_notice_bar)
13
+ config.widget_types.register(Pageflow::BuiltInWidgetType.unmute_button, default: true)
9
14
  end
10
15
  end
11
16
  end
@@ -24,11 +24,37 @@ module Pageflow
24
24
  # @return {Boolean}
25
25
  attr_reader :top_level_type
26
26
 
27
+ # Callable that receives a file record and returns a hash of one
28
+ # of the following forms:
29
+ #
30
+ # {
31
+ # poster: "url/of/image"
32
+ # }
33
+ #
34
+ # where `poster` is an arbitrary css class infix. Use `default` to
35
+ # skip the infix in the generated css class name. Or
36
+ #
37
+ # {
38
+ # poster: {
39
+ # desktop: "desktop/url/of/image",
40
+ # mobile: "mobile/url/of/image"
41
+ # }
42
+ # }
43
+ #
44
+ # to provide different urls for the two media breakpoints.
45
+ #
46
+ # @return [#call]
47
+ attr_reader :css_background_image_urls
48
+
27
49
  # Callable that returns a hash of url template strings indexed by
28
50
  # their names.
29
51
  # @return [#call]
30
52
  attr_reader :url_templates
31
53
 
54
+ # Attributes that are custom to this file type.
55
+ # @return {Array<Symbol>}
56
+ attr_reader :custom_attributes
57
+
32
58
  # Create file type to be returned in {PageType#file_types}.
33
59
  #
34
60
  # @example
@@ -50,9 +76,14 @@ module Pageflow
50
76
  # model `Pageflow::Rainbow::File`.
51
77
  # @option options [Array<FileType>] :nested_file_types
52
78
  # Optional. Array of FileTypes allowed for nested files. Defaults to [].
79
+ # @option options [#call] :css_background_image_urls
80
+ # Optional. See {#css_background_image_urls}
53
81
  # @option options [#call] :url_templates
54
82
  # Optional. Callable returning a hash of url template strings
55
83
  # indexed by their names.
84
+ # @option options [Array<Symbol>] :custom_attributes
85
+ # Optional. Array of strings containing attribute names that are
86
+ # custom to this file type
56
87
  def initialize(options)
57
88
  @model_string_or_reference = options.fetch(:model)
58
89
  @partial = options[:partial]
@@ -60,7 +91,10 @@ module Pageflow
60
91
  @collection_name_or_blank = options[:collection_name]
61
92
  @nested_file_types = options.fetch(:nested_file_types, [])
62
93
  @top_level_type = options.fetch(:top_level_type, false)
94
+ @css_background_image_urls = options[:css_background_image_urls]
95
+ @css_background_image_class_prefix = options[:css_background_image_class_prefix]
63
96
  @url_templates = options.fetch(:url_templates, ->() { {} })
97
+ @custom_attributes = options.fetch(:custom_attributes, [])
64
98
  end
65
99
 
66
100
  # ActiveRecord model that represents the files of this type.
@@ -84,6 +118,11 @@ module Pageflow
84
118
  end
85
119
  end
86
120
 
121
+ # @api private
122
+ def css_background_image_class_prefix
123
+ @css_background_image_class_prefix || model.model_name.singular
124
+ end
125
+
87
126
  # @api private
88
127
  def param_key
89
128
  model.model_name.param_key.to_sym
@@ -31,6 +31,12 @@ module Pageflow
31
31
  end
32
32
  end
33
33
 
34
+ def with_css_background_image_support
35
+ select do |file_type|
36
+ file_type.css_background_image_urls.present?
37
+ end
38
+ end
39
+
34
40
  private
35
41
 
36
42
  def search_for_nested_file_types(higher_level_file_types)
@@ -1,3 +1,3 @@
1
1
  module Pageflow
2
- VERSION = '13.0.0.beta6'.freeze
2
+ VERSION = '13.0.0.beta7'.freeze
3
3
  end
@@ -1,11 +1,17 @@
1
1
  module Pageflow
2
- class TestHostedFile < ActiveRecord::Base
3
- self.table_name = 'test_hosted_files'
4
- include HostedFile
5
- end
6
-
7
2
  FactoryBot.define do
8
- factory :hosted_file, class: TestHostedFile do
3
+ factory :hosted_file, class: 'Pageflow::TestHostedFile' do
4
+ attachment_on_s3 { File.open(Engine.root.join('spec', 'fixtures', 'image.png')) }
5
+ state { 'uploaded_to_s3' }
6
+
7
+ transient do
8
+ used_in { nil }
9
+ end
10
+
11
+ after(:create) do |file, evaluator|
12
+ create(:file_usage, file: file, revision: evaluator.used_in) if evaluator.used_in
13
+ end
14
+
9
15
  trait :on_filesystem do
10
16
  attachment_on_filesystem { File.open(Engine.root.join('spec', 'fixtures', 'image.png')) }
11
17
  attachment_on_s3 { nil }
@@ -19,8 +25,6 @@ module Pageflow
19
25
  end
20
26
 
21
27
  trait :uploaded_to_s3 do
22
- attachment_on_s3 { File.open(Engine.root.join('spec', 'fixtures', 'image.png')) }
23
- state { 'uploaded_to_s3' }
24
28
  end
25
29
 
26
30
  trait :with_overridden_keep_on_filesystem do
@@ -4,9 +4,9 @@
4
4
  * License MIT (c) Zohar Arad 2013
5
5
  */
6
6
  (function ($win, ns, factory) {
7
+ "use strict";
7
8
  /*global define */
8
9
  /*global swfobject */
9
- "use strict";
10
10
 
11
11
  if (typeof (module) !== 'undefined' && module.exports) { // CommonJS
12
12
  module.exports = factory(ns, $win);
@@ -134,17 +134,35 @@
134
134
  trigger: function (evt) {
135
135
  if (this.channels && this.channels.hasOwnProperty(evt)) {
136
136
  var args = Array.prototype.slice.call(arguments, 1);
137
- var a = [];
137
+
138
+ // temp array to store all subscribers for the channel evt that must be stored in state
139
+ var evtSubscribers = [];
140
+ // temp array to store all subscribers for the channel evt that is being triggered that must be executed.
141
+ var evtSubscribersForExec = [];
142
+
138
143
  while(this.channels[evt].length > 0) {
139
144
  var sub = this.channels[evt].shift();
140
- if (typeof (sub.fn) === 'function') {
141
- sub.fn.apply(sub.ctx, args);
142
- }
145
+
143
146
  if ( !sub.once ){
144
- a.push(sub);
147
+ // for any channel event triggers that are not marked as once only, we need to keep.
148
+ evtSubscribers.push(sub);
145
149
  }
150
+
151
+ // if we have a function with the channel subscription, store in temp array ready to call after all proceeded
152
+ if (typeof (sub.fn) === 'function') {
153
+ // every subscriber we've processed will be executed
154
+ evtSubscribersForExec.push(sub);
155
+ }
156
+ }
157
+
158
+ // before we execute any of the subscribers to the trigger, we must make sure keep this.channels[evt] up to date in case any sub fns call off
159
+ this.channels[evt] = evtSubscribers;
160
+
161
+ // run all the event subscribers that need executing
162
+ while(evtSubscribersForExec.length > 0) {
163
+ var eventSub = evtSubscribersForExec.shift();
164
+ eventSub.fn.apply(eventSub.ctx, args);
146
165
  }
147
- this.channels[evt] = a;
148
166
  }
149
167
  }
150
168
  };
@@ -153,19 +171,21 @@
153
171
  /**
154
172
  * Flash embed code string with cross-browser support.
155
173
  */
156
- flash_embed_code: (function () {
174
+ flash_embed_code: function (id, swf_location, ts) {
157
175
  var prefix;
158
- var s = '<param name="movie" value="$2?playerInstance=window.' + ns + '_flash.instances[\'$1\']&datetime=$3"/>' +
176
+ var elemId = ns + id;
177
+ var s = '<param name="movie" value="' + swf_location + '?playerInstanceNumber=' + id + '&datetime=' + ts + '"/>' +
159
178
  '<param name="wmode" value="transparent"/>' +
160
179
  '<param name="allowscriptaccess" value="always" />' +
161
180
  '</object>';
162
181
  if (ActiveXObject) {
163
- prefix = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="1" height="1" id="$1">';
182
+ prefix = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="1" height="1" id="' + elemId + '">';
164
183
  } else {
165
- prefix = '<object type="application/x-shockwave-flash" data="$2?playerInstance=window.' + ns + '_flash.instances[\'$1\']&datetime=$3" width="1" height="1" id="$1" >';
184
+ prefix = '<object type="application/x-shockwave-flash" data="' + swf_location + '?playerInstanceNumber=' + id + '&datetime=' + ts + '" width="1" height="1" id="' + elemId + '" >';
166
185
  }
186
+
167
187
  return prefix + s;
168
- }()),
188
+ },
169
189
  /**
170
190
  * Check if browser supports audio mime type.
171
191
  * @param {String} mime_type audio mime type to check
@@ -175,30 +195,36 @@
175
195
  var a = document.createElement('audio');
176
196
  var mime_str;
177
197
  switch (mime_type) {
178
- case 'mp3':
179
- mime_str = 'audio/mpeg; codecs="mp3"';
180
- break;
181
- case 'vorbis':
182
- mime_str = 'audio/ogg; codecs="vorbis"';
183
- break;
184
- case 'opus':
185
- mime_str = 'audio/ogg; codecs="opus"';
186
- break;
187
- case 'webm':
188
- mime_str = 'audio/webm; codecs="vorbis"';
189
- break;
190
- case 'mp4':
191
- mime_str = 'audio/mp4; codecs="mp4a.40.5"';
192
- break;
193
- case 'wav':
194
- mime_str = 'audio/wav; codecs="1"';
195
- break;
196
- }
197
- if (mime_str === undefined) {
198
- throw new Error('Unspecified Audio Mime Type');
199
- } else {
200
- return !!a.canPlayType && a.canPlayType(mime_str) !== '';
198
+ case 'mp3':
199
+ mime_str = 'audio/mpeg;';
200
+ break;
201
+ case 'vorbis':
202
+ mime_str = 'audio/ogg; codecs="vorbis"';
203
+ break;
204
+ case 'opus':
205
+ mime_str = 'audio/ogg; codecs="opus"';
206
+ break;
207
+ case 'webm':
208
+ mime_str = 'audio/webm; codecs="vorbis"';
209
+ break;
210
+ case 'mp4':
211
+ mime_str = 'audio/mp4; codecs="mp4a.40.5"';
212
+ break;
213
+ case 'wav':
214
+ mime_str = 'audio/wav; codecs="1"';
215
+ break;
201
216
  }
217
+ if (mime_str !== undefined) {
218
+ if (mime_type === 'mp3' && navigator.userAgent.match(/Android/i) && navigator.userAgent.match(/Firefox/i)) {
219
+ return true;
220
+ }
221
+ try {
222
+ return !!a.canPlayType && a.canPlayType(mime_str) !== '';
223
+ } catch (e) {
224
+ return false;
225
+ }
226
+ }
227
+ return false;
202
228
  },
203
229
  /**
204
230
  * Boolean flag indicating whether the browser has Flash installed or not
@@ -232,19 +258,17 @@
232
258
  document.body.appendChild(d);
233
259
  if(typeof($win.swfobject) === 'object'){
234
260
  var fv = {
235
- playerInstance: 'window.'+ ns + '_flash.instances["'+id+'"]'
261
+ playerInstance: 'window.'+ ns + '_flash.instances[\''+id+'\']'
236
262
  };
237
263
  var params = {
238
264
  allowscriptaccess: 'always',
239
265
  wmode: 'transparent'
240
266
  };
241
- d.innerHTML = '<div id="'+id+'"></div>';
242
- swfobject.embedSWF(swf_location + '?ts='+(new Date().getTime() + Math.random()), id, "1", "1", "9.0.0", null, fv, params);
267
+ d.innerHTML = '<div id="'+ ns + id +'"></div>';
268
+ swfobject.embedSWF(swf_location + '?ts='+(new Date().getTime() + Math.random()), ns + id, "1", "1", "9.0.0", null, fv, params);
243
269
  } else {
244
- var flashSource = this.flash_embed_code.replace(/\$1/g, id);
245
- flashSource = flashSource.replace(/\$2/g, swf_location);
246
- flashSource = flashSource.replace(/\$3/g, (new Date().getTime() + Math.random())); // Ensure swf is not pulled from cache
247
- d.innerHTML = flashSource;
270
+ var ts = new Date().getTime() + Math.random(); // Ensure swf is not pulled from cache
271
+ d.innerHTML = this.flash_embed_code(id, swf_location, ts);
248
272
  }
249
273
  return document.getElementById(id);
250
274
  },
@@ -281,7 +305,7 @@
281
305
  duration: 0, /** {Float} audio duration (sec) */
282
306
  position: 0, /** {Float} audio position (sec) */
283
307
  load_percent: 0, /** {Float} audio file load percent (%) */
284
- seekable: null, /** {Boolean} is loaded audio seekable */
308
+ seekable: false, /** {Boolean} is loaded audio seekable */
285
309
  ready: null /** {Boolean} is loaded audio seekable */
286
310
  };
287
311
 
@@ -291,8 +315,7 @@
291
315
  * @type {Object}
292
316
  */
293
317
  var globalAudio5Flash = $win[ns + '_flash'] = $win[ns + '_flash'] || {
294
- instances: { }, /** FlashAudioPlayer instance hash */
295
- count: 0 /** FlashAudioPlayer instance count */
318
+ instances: [] /** FlashAudioPlayer instance hash */
296
319
  };
297
320
 
298
321
  /**
@@ -311,9 +334,8 @@
311
334
  * @param {String} swf_src path to audio player SWF file
312
335
  */
313
336
  init: function (swf_src) {
314
- globalAudio5Flash.count += 1;
315
- this.id = ns + globalAudio5Flash.count;
316
- globalAudio5Flash.instances[this.id] = this;
337
+ globalAudio5Flash.instances.push(this);
338
+ this.id = globalAudio5Flash.instances.length - 1;
317
339
  this.embed(swf_src);
318
340
  },
319
341
  /**
@@ -327,7 +349,7 @@
327
349
  * ExternalInterface callback indicating SWF is ready
328
350
  */
329
351
  eiReady: function () {
330
- this.audio = document.getElementById(this.id);
352
+ this.audio = document.getElementById(ns + this.id);
331
353
  this.trigger('ready');
332
354
  },
333
355
  /**
@@ -363,9 +385,13 @@
363
385
  /**
364
386
  * ExternalInterface download progress callback. Fires as long as audio file is downloaded by browser.
365
387
  * @param {Float} percent audio download percent
388
+ * @param {Float} duration audio total duration (sec)
389
+ * * @param {Boolean} seekable is audio seekable or not (download or streaming)
366
390
  */
367
- eiProgress: function (percent) {
391
+ eiProgress: function (percent, duration, seekable) {
368
392
  this.load_percent = percent;
393
+ this.duration = duration;
394
+ this.seekable = seekable;
369
395
  this.trigger('progress', percent);
370
396
  },
371
397
  /**
@@ -381,6 +407,7 @@
381
407
  eiPlay: function () {
382
408
  this.playing = true;
383
409
  this.trigger('play');
410
+ this.trigger('playing');
384
411
  },
385
412
  /**
386
413
  * ExternalInterface audio pause callback. Fires when audio is paused.
@@ -393,7 +420,7 @@
393
420
  * ExternalInterface audio ended callback. Fires when audio playback ended.
394
421
  */
395
422
  eiEnded: function () {
396
- this.playing = false;
423
+ this.pause();
397
424
  this.trigger('ended');
398
425
  },
399
426
  /**
@@ -459,6 +486,24 @@
459
486
  this.audio.seekTo(position);
460
487
  this.position = position;
461
488
  } catch (e) {}
489
+ },
490
+ /**
491
+ * This feature was not implemented for Flash
492
+ */
493
+ rate: function () {
494
+ // Not implemented
495
+ },
496
+ /**
497
+ * Destroy audio object and remove from DOM
498
+ */
499
+ destroyAudio: function() {
500
+ if(this.audio){
501
+ this.pause();
502
+ this.audio.parentNode.removeChild(this.audio);
503
+ delete globalAudio5Flash.instances[this.id];
504
+ globalAudio5Flash.instances.splice(this.id, 1);
505
+ delete this.audio;
506
+ }
462
507
  }
463
508
  };
464
509
 
@@ -475,52 +520,91 @@
475
520
  /**
476
521
  * Initialize the player instance
477
522
  */
478
- init: function (reusedTag) {
479
- this.reusedTag = reusedTag;
523
+ init: function () {
524
+ this._rate = 1;
480
525
  this.trigger('ready');
481
526
  },
527
+ /**
528
+ * Create new audio instance
529
+ */
482
530
  createAudio: function(){
483
- this.audio = this.reusedTag || new Audio();
531
+ this.audio = new Audio();
484
532
  this.audio.autoplay = false;
485
533
  this.audio.preload = 'auto';
486
534
  this.audio.autobuffer = true;
535
+ this.audio.playbackRate = this._rate;
487
536
 
488
537
  this.audio.setAttribute('crossorigin', 'anonymous');
489
538
 
490
539
  this.bindEvents();
491
540
  },
541
+ /**
542
+ * Destroy current audio instance
543
+ */
492
544
  destroyAudio: function(){
493
545
  if(this.audio){
546
+ this.pause();
494
547
  this.unbindEvents();
495
- delete this.audio;
548
+ try {
549
+ this.audio.setAttribute('src', '');
550
+ } finally {
551
+ delete this.audio;
552
+ }
496
553
  }
497
554
  },
555
+ /**
556
+ * Sets up audio event listeners once so adding / removing event listeners is always done
557
+ * on the same callbacks.
558
+ */
559
+ setupEventListeners: function(){
560
+ this.listeners = {
561
+ loadstart: this.onLoadStart.bind(this),
562
+ canplay: this.onLoad.bind(this),
563
+ loadedmetadata: this.onLoadedMetadata.bind(this),
564
+ play: this.onPlay.bind(this),
565
+ playing: this.onPlaying.bind(this),
566
+ pause: this.onPause.bind(this),
567
+ ended: this.onEnded.bind(this),
568
+ error: this.onError.bind(this),
569
+ timeupdate: this.onTimeUpdate.bind(this),
570
+ seeking: this.onSeeking.bind(this),
571
+ seeked: this.onSeeked.bind(this)
572
+ };
573
+ },
498
574
  /**
499
575
  * Bind DOM events to Audio object
500
576
  */
501
- bindEvents: function () {
502
- this.audio.addEventListener('loadstart', this.onLoadStart.bind(this));
503
- this.audio.addEventListener('canplay', this.onLoad.bind(this));
504
- this.audio.addEventListener('loadedmetadata', this.onLoadedMetadata.bind(this));
505
- this.audio.addEventListener('playing', this.onPlay.bind(this));
506
- this.audio.addEventListener('pause', this.onPause.bind(this));
507
- this.audio.addEventListener('ended', this.onEnded.bind(this));
508
- this.audio.addEventListener('error', this.onError.bind(this));
509
- this.audio.addEventListener('timeupdate', this.onTimeUpdate.bind(this));
510
- this.audio.addEventListener('seeking', this.onSeeking.bind(this));
511
- this.audio.addEventListener('seeked', this.onSeeked.bind(this));
512
- },
513
- unbindEvents: function(){
514
- this.audio.removeEventListener('loadstart', this.onLoadStart.bind(this));
515
- this.audio.removeEventListener('canplay', this.onLoad.bind(this));
516
- this.audio.removeEventListener('loadedmetadata', this.onLoadedMetadata.bind(this));
517
- this.audio.removeEventListener('playing', this.onPlay.bind(this));
518
- this.audio.removeEventListener('pause', this.onPause.bind(this));
519
- this.audio.removeEventListener('ended', this.onEnded.bind(this));
520
- this.audio.removeEventListener('error', this.onError.bind(this));
521
- this.audio.removeEventListener('timeupdate', this.onTimeUpdate.bind(this));
522
- this.audio.removeEventListener('seeking', this.onSeeking.bind(this));
523
- this.audio.removeEventListener('seeked', this.onSeeked.bind(this));
577
+ bindEvents: function() {
578
+ if(this.listeners === undefined){
579
+ this.setupEventListeners();
580
+ }
581
+ this.audio.addEventListener('loadstart', this.listeners.loadstart, false);
582
+ this.audio.addEventListener('canplay', this.listeners.canplay, false);
583
+ this.audio.addEventListener('loadedmetadata', this.listeners.loadedmetadata, false);
584
+ this.audio.addEventListener('play', this.listeners.play, false);
585
+ this.audio.addEventListener('playing', this.listeners.playing, false);
586
+ this.audio.addEventListener('pause', this.listeners.pause, false);
587
+ this.audio.addEventListener('ended', this.listeners.ended, false);
588
+ this.audio.addEventListener('error', this.listeners.error, false);
589
+ this.audio.addEventListener('timeupdate', this.listeners.timeupdate, false);
590
+ this.audio.addEventListener('seeking', this.listeners.seeking, false);
591
+ this.audio.addEventListener('seeked', this.listeners.seeked, false);
592
+ },
593
+ /**
594
+ * Unbind DOM events from Audio object
595
+ */
596
+ unbindEvents: function() {
597
+ this.audio.removeEventListener('loadstart', this.listeners.loadstart);
598
+ this.audio.removeEventListener('canplay', this.listeners.canplay);
599
+ this.audio.removeEventListener('loadedmetadata', this.listeners.loadedmetadata);
600
+ this.audio.removeEventListener('play', this.listeners.play);
601
+ this.audio.removeEventListener('playing', this.listeners.playing);
602
+ this.audio.removeEventListener('pause', this.listeners.pause);
603
+ this.audio.removeEventListener('ended', this.listeners.ended);
604
+ this.audio.removeEventListener('error', this.listeners.error);
605
+ this.audio.removeEventListener('timeupdate', this.listeners.timeupdate);
606
+ this.audio.removeEventListener('seeking', this.listeners.seeking);
607
+ this.audio.removeEventListener('seeked', this.listeners.seeked);
524
608
  },
525
609
  /**
526
610
  * Audio load start event handler. Triggered when audio starts loading
@@ -533,6 +617,9 @@
533
617
  * Resets player parameters and starts audio download progress timer.
534
618
  */
535
619
  onLoad: function () {
620
+ if(!this.audio){
621
+ return setTimeout(this.onLoad.bind(this), 100);
622
+ }
536
623
  this.seekable = this.audio.seekable && this.audio.seekable.length > 0;
537
624
  if (this.seekable) {
538
625
  this.timer = setInterval(this.onProgress.bind(this), 250);
@@ -549,12 +636,21 @@
549
636
  * Audio play event handler. Triggered when audio starts playing.
550
637
  */
551
638
  onPlay: function () {
639
+ this.playing = true;
552
640
  this.trigger('play');
553
641
  },
642
+ /**
643
+ * Audio play event handler. Triggered when audio starts playing.
644
+ */
645
+ onPlaying: function () {
646
+ this.playing = true;
647
+ this.trigger('playing');
648
+ },
554
649
  /**
555
650
  * Audio pause event handler. Triggered when audio is paused.
556
651
  */
557
652
  onPause: function () {
653
+ this.playing = false;
558
654
  this.trigger('pause');
559
655
  },
560
656
  /**
@@ -568,9 +664,11 @@
568
664
  * Audio timeupdate event handler. Triggered as long as playhead position is updated (audio is being played).
569
665
  */
570
666
  onTimeUpdate: function () {
571
- if (this.audio.buffered !== null && this.playing) {
572
- this.position = this.audio.currentTime;
573
- this.duration = this.audio.duration === Infinity ? null : this.audio.duration;
667
+ if (this.audio && this.playing) {
668
+ try{
669
+ this.position = this.audio.currentTime;
670
+ this.duration = this.audio.duration === Infinity ? null : this.audio.duration;
671
+ } catch (e){}
574
672
  this.trigger('timeupdate', this.position, this.duration);
575
673
  }
576
674
  },
@@ -580,8 +678,9 @@
580
678
  * Cancelled when audio has fully download or when a new audio file has been loaded to the player.
581
679
  */
582
680
  onProgress: function () {
583
- if (this.audio.buffered !== null) {
584
- this.load_percent = parseInt(((this.audio.buffered.end(this.audio.buffered.length - 1) / this.audio.duration) * 100), 10);
681
+ if (this.audio && this.audio.buffered !== null && this.audio.buffered.length) {
682
+ this.duration = this.audio.duration === Infinity ? null : this.audio.duration;
683
+ this.load_percent = parseInt(((this.audio.buffered.end(this.audio.buffered.length - 1) / this.duration) * 100), 10);
585
684
  this.trigger('progress', this.load_percent);
586
685
  if (this.load_percent >= 100) {
587
686
  this.clearLoadProgress();
@@ -632,8 +731,11 @@
632
731
  */
633
732
  load: function (url) {
634
733
  this.reset();
635
- this.destroyAudio();
636
- this.createAudio();
734
+ this.trigger('pause');
735
+ //this.destroyAudio();
736
+ if(this.audio === undefined){
737
+ this.createAudio();
738
+ }
637
739
  this.audio.setAttribute('src', url);
638
740
  this.audio.load();
639
741
  },
@@ -641,15 +743,19 @@
641
743
  * Play audio
642
744
  */
643
745
  play: function () {
644
- this.playing = true;
645
- this.audio.play();
746
+ if(this.audio) {
747
+ var playPromise = this.audio.play();
748
+ this.audio.playbackRate = this._rate;
749
+ return playPromise;
750
+ }
646
751
  },
647
752
  /**
648
753
  * Pause audio
649
754
  */
650
755
  pause: function () {
651
- this.playing = false;
652
- this.audio.pause();
756
+ if(this.audio) {
757
+ this.audio.pause();
758
+ }
653
759
  },
654
760
  /**
655
761
  * Get / Set audio volume
@@ -676,10 +782,23 @@
676
782
  if (playing) {
677
783
  this.play();
678
784
  } else {
679
- if (this.audio.buffered !== null) {
785
+ if (this.audio.buffered !== null && this.audio.buffered.length) {
680
786
  this.trigger('timeupdate', this.position, this.duration);
681
787
  }
682
788
  }
789
+ },
790
+ /**
791
+ * Define the playback rate
792
+ * @param {Float} v playback rate value to be set
793
+ */
794
+ rate: function (v) {
795
+ if (v === undefined || isNaN(parseFloat(v))) {
796
+ return this._rate;
797
+ }
798
+ this._rate = v;
799
+ if (this.audio) {
800
+ this.audio.playbackRate = v;
801
+ }
683
802
  }
684
803
  };
685
804
 
@@ -748,7 +867,7 @@
748
867
  if (this.settings.use_flash) {
749
868
  this.audio.init(s.swf_path);
750
869
  } else {
751
- this.audio.init(s.reusedTag);
870
+ this.audio.init();
752
871
  }
753
872
  },
754
873
  /**
@@ -757,27 +876,35 @@
757
876
  * @return {FlashAudioPlayer,HTML5AudioPlayer} audio player instance
758
877
  */
759
878
  getPlayer: function () {
760
- var i, l, player;
761
- for (i = 0, l = this.settings.codecs.length; i < l; i++) {
762
- var codec = this.settings.codecs[i];
763
- if (Audio5js.can_play(codec)) {
764
- player = new HTML5AudioPlayer();
765
- this.settings.use_flash = false;
766
- this.settings.player = {
767
- engine: 'html',
768
- codec: codec
769
- };
770
- break;
771
- }
772
- }
773
- if (player === undefined) {
774
- // here we double check for mp3 support instead of defaulting to Flash in case user overrode the settings.codecs array with an empty array.
775
- this.settings.use_flash = !Audio5js.can_play('mp3');
776
- player = this.settings.use_flash ? new FlashAudioPlayer() : new HTML5AudioPlayer();
879
+ var i, l, player, codec;
880
+ if(this.settings.use_flash){
881
+ player = new FlashAudioPlayer();
777
882
  this.settings.player = {
778
- engine: (this.settings.use_flash ? 'flash' : 'html'),
883
+ engine: 'flash',
779
884
  codec: 'mp3'
780
885
  };
886
+ } else {
887
+ for (i = 0, l = this.settings.codecs.length; i < l; i++) {
888
+ codec = this.settings.codecs[i];
889
+ if (Audio5js.can_play(codec)) {
890
+ player = new HTML5AudioPlayer();
891
+ this.settings.use_flash = false;
892
+ this.settings.player = {
893
+ engine: 'html',
894
+ codec: codec
895
+ };
896
+ break;
897
+ }
898
+ }
899
+ if (player === undefined) {
900
+ // here we double check for mp3 support instead of defaulting to Flash in case user overrode the settings.codecs array with an empty array.
901
+ this.settings.use_flash = !Audio5js.can_play('mp3');
902
+ player = this.settings.use_flash ? new FlashAudioPlayer() : new HTML5AudioPlayer();
903
+ this.settings.player = {
904
+ engine: (this.settings.use_flash ? 'flash' : 'html'),
905
+ codec: 'mp3'
906
+ };
907
+ }
781
908
  }
782
909
  return player;
783
910
  },
@@ -798,18 +925,36 @@
798
925
  this.audio.on('seeking', this.onSeeking, this);
799
926
  this.audio.on('seeked', this.onSeeked, this);
800
927
  },
928
+ /**
929
+ * Bind events from audio object to internal callbacks
930
+ */
931
+ unbindAudioEvents: function () {
932
+ this.audio.off('ready', this.onReady);
933
+ this.audio.off('loadstart', this.onLoadStart);
934
+ this.audio.off('loadedmetadata', this.onLoadedMetadata);
935
+ this.audio.off('play', this.onPlay);
936
+ this.audio.off('pause', this.onPause);
937
+ this.audio.off('ended', this.onEnded);
938
+ this.audio.off('canplay', this.onCanPlay);
939
+ this.audio.off('timeupdate', this.onTimeUpdate);
940
+ this.audio.off('progress', this.onProgress);
941
+ this.audio.off('error', this.onError);
942
+ this.audio.off('seeking', this.onSeeking);
943
+ this.audio.off('seeked', this.onSeeked);
944
+ },
801
945
  /**
802
946
  * Load audio from URL
803
947
  * @param {String} url URL of audio to load
804
948
  */
805
949
  load: function (url) {
950
+ var that = this;
806
951
  var f = function(u){
807
- this.audio.load(u);
808
- this.trigger('load');
809
- }.bind(this, url);
952
+ that.audio.load(u);
953
+ that.trigger('load');
954
+ };
810
955
 
811
956
  if(this.ready){
812
- f();
957
+ f(url);
813
958
  } else {
814
959
  this.on('ready', f);
815
960
  }
@@ -819,8 +964,7 @@
819
964
  */
820
965
  play: function () {
821
966
  if(!this.playing){
822
- this.playing = true;
823
- this.audio.play();
967
+ return this.audio.play();
824
968
  }
825
969
  },
826
970
  /**
@@ -828,7 +972,6 @@
828
972
  */
829
973
  pause: function () {
830
974
  if(this.playing){
831
- this.playing = false;
832
975
  this.audio.pause();
833
976
  }
834
977
  },
@@ -859,6 +1002,20 @@
859
1002
  this.audio.seek(position);
860
1003
  this.position = position;
861
1004
  },
1005
+ /**
1006
+ * Define the playback rate
1007
+ * @param {Float} value playback rate value to be set
1008
+ */
1009
+ rate: function (value) {
1010
+ return this.audio.rate(value);
1011
+ },
1012
+ /**
1013
+ * Destroy audio object and remove from DOM
1014
+ */
1015
+ destroy: function() {
1016
+ this.unbindAudioEvents();
1017
+ this.audio.destroyAudio();
1018
+ },
862
1019
  /**
863
1020
  * Callback for audio ready event. Indicates audio is ready for playback.
864
1021
  * Looks for ready callback in settings object and invokes it in the context of player instance
@@ -886,27 +1043,22 @@
886
1043
  * Audio play event handler
887
1044
  */
888
1045
  onPlay: function () {
1046
+ this.playing = true;
889
1047
  this.trigger('play');
890
1048
  },
891
1049
  /**
892
1050
  * Audio pause event handler
893
1051
  */
894
1052
  onPause: function () {
1053
+ this.playing = false;
895
1054
  this.trigger('pause');
896
1055
  },
897
1056
  /**
898
1057
  * Playback end event handler
899
1058
  */
900
1059
  onEnded: function () {
901
- var wasPlaying = this.playing;
902
-
903
- if (this.settings.loop && wasPlaying) {
904
- this.audio.play();
905
- }
906
- else {
907
- this.playing = false;
908
- this.trigger('ended');
909
- }
1060
+ this.playing = false;
1061
+ this.trigger('ended');
910
1062
  },
911
1063
  /**
912
1064
  * Audio error event handler
@@ -944,9 +1096,7 @@
944
1096
  */
945
1097
  onTimeUpdate: function (position, duration) {
946
1098
  this.position = this.settings.format_time ? util.formatTime(position) : position;
947
- if (this.duration !== duration) {
948
- this.duration = this.settings.format_time && duration !== null ? util.formatTime(duration) : duration;
949
- }
1099
+ this.duration = this.settings.format_time && duration !== null ? util.formatTime(duration) : duration;
950
1100
  this.trigger('timeupdate', this.position, this.duration);
951
1101
  },
952
1102
  /**
@@ -954,6 +1104,7 @@
954
1104
  * @param {Float} loaded audio download percent
955
1105
  */
956
1106
  onProgress: function (loaded) {
1107
+ this.duration = this.audio.duration;
957
1108
  this.load_percent = loaded;
958
1109
  this.trigger('progress', loaded);
959
1110
  }
@@ -964,4 +1115,4 @@
964
1115
 
965
1116
  return Audio5js;
966
1117
 
967
- }));
1118
+ }));