pageflow 12.2.0 → 12.3.0

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -92
  3. data/admins/pageflow/accounts.rb +1 -0
  4. data/app/assets/audios/pageflow/unmute.mp3 +0 -0
  5. data/app/assets/javascripts/pageflow/asset_urls.js.erb +1 -1
  6. data/app/assets/javascripts/pageflow/audio/multi_player.js +4 -0
  7. data/app/assets/javascripts/pageflow/audio_player.js +1 -1
  8. data/app/assets/javascripts/pageflow/background_media.js +22 -0
  9. data/app/assets/javascripts/pageflow/base.js +2 -0
  10. data/app/assets/javascripts/pageflow/browser/agent.js +92 -78
  11. data/app/assets/javascripts/pageflow/browser/autoplay_support.js +2 -2
  12. data/app/assets/javascripts/pageflow/cookie_notice.js +7 -0
  13. data/app/assets/javascripts/pageflow/dist/react.js +1312 -329
  14. data/app/assets/javascripts/pageflow/editor/models/configuration.js +5 -5
  15. data/app/assets/javascripts/pageflow/editor/templates/background_positioning_sliders.jst.ejs +8 -0
  16. data/app/assets/javascripts/pageflow/editor/views/background_positioning_sliders_view.js +37 -23
  17. data/app/assets/javascripts/pageflow/editor/views/background_positioning_view.js +2 -2
  18. data/app/assets/javascripts/pageflow/editor/views/configuration_editors/video.js +1 -1
  19. data/app/assets/javascripts/pageflow/editor/views/edit_widget_view.js +9 -0
  20. data/app/assets/javascripts/pageflow/editor/views/info_box_view.js +8 -0
  21. data/app/assets/javascripts/pageflow/editor/views/widget_types/cookie_notice_bar.js +15 -0
  22. data/app/assets/javascripts/pageflow/media_player.js +7 -3
  23. data/app/assets/javascripts/pageflow/media_player/handle_failed_play.js +34 -0
  24. data/app/assets/javascripts/pageflow/media_player/volume_fading/web_audio.js +29 -3
  25. data/app/assets/javascripts/pageflow/seed_entry_data.js +3 -3
  26. data/app/assets/javascripts/pageflow/slideshow.js +17 -9
  27. data/app/assets/javascripts/pageflow/slideshow/adjacent_pages.js +7 -2
  28. data/app/assets/javascripts/pageflow/slideshow/adjacent_preloader.js +26 -0
  29. data/app/assets/javascripts/pageflow/slideshow/atmo.js +23 -12
  30. data/app/assets/javascripts/pageflow/slideshow/lazy_page_widget.js +2 -2
  31. data/app/assets/javascripts/pageflow/slideshow/{adjacent_preparer.js → successor_preparer.js} +14 -11
  32. data/app/assets/javascripts/pageflow/ui/views/configuration_editor_view.js +2 -2
  33. data/app/assets/javascripts/pageflow/video_player/lazy.js +1 -1
  34. data/app/assets/javascripts/pageflow/visited.js +2 -0
  35. data/app/assets/stylesheets/pageflow/editor/background_positioning.scss +34 -10
  36. data/app/assets/stylesheets/pageflow/page_types/video.scss +1 -4
  37. data/app/assets/stylesheets/pageflow/page_types/video/mobile_poster.scss +15 -0
  38. data/app/assets/stylesheets/pageflow/themes/default/background_media_unmute_button.scss +68 -0
  39. data/app/assets/stylesheets/pageflow/themes/default/base.scss +2 -0
  40. data/app/assets/stylesheets/pageflow/themes/default/cookie_notice_bar.scss +57 -0
  41. data/app/assets/stylesheets/pageflow/themes/default/page.scss +1 -0
  42. data/app/assets/stylesheets/pageflow/themes/default/page/hyphenate.scss +24 -0
  43. data/app/assets/stylesheets/pageflow/themes/default/slideshow.scss +1 -0
  44. data/app/controllers/pageflow/admin/initial_passwords_controller.rb +8 -0
  45. data/app/helpers/pageflow/common_entry_seed_helper.rb +1 -0
  46. data/app/helpers/pageflow/entries_helper.rb +20 -2
  47. data/app/helpers/pageflow/entry_json_seed_helper.rb +1 -1
  48. data/app/helpers/pageflow/public_i18n_helper.rb +6 -1
  49. data/app/views/admin/accounts/_form.html.erb +1 -0
  50. data/app/views/pageflow/admin/initial_passwords/edit.html.erb +16 -0
  51. data/app/views/pageflow/entry_json_seed/_entry.json.jbuilder +1 -1
  52. data/app/views/pageflow/user_mailer/invitation.html.erb +2 -2
  53. data/app/views/pageflow/user_mailer/invitation.text.erb +1 -1
  54. data/config/locales/de.yml +14 -6
  55. data/config/locales/en.yml +16 -8
  56. data/config/routes.rb +6 -0
  57. data/db/migrate/20180528144334_add_privacy_link_url_to_themings.rb +5 -0
  58. data/lib/pageflow/built_in_widget_type.rb +8 -0
  59. data/lib/pageflow/built_in_widget_types_plugin.rb +2 -0
  60. data/lib/pageflow/version.rb +1 -1
  61. data/vendor/assets/javascripts/audio5.min.js +280 -129
  62. metadata +19 -7
  63. data/app/assets/javascripts/pageflow/media_player/catch_play_promise.js +0 -23
  64. data/app/assets/javascripts/pageflow/slideshow/progressive_preload.js +0 -42
@@ -1,3 +1,4 @@
1
1
  .slideshow {
2
2
  @extend %pageflow_widget_margin_top !optional;
3
+ @extend %pageflow_widget_margin_bottom !optional;
3
4
  }
@@ -0,0 +1,8 @@
1
+ module Pageflow
2
+ module Admin
3
+ class InitialPasswordsController < Devise::PasswordsController
4
+ layout 'active_admin_logged_out'
5
+ helper ActiveAdmin::ViewHelpers
6
+ end
7
+ end
8
+ end
@@ -10,6 +10,7 @@ module Pageflow
10
10
  {
11
11
  locale: entry.locale,
12
12
  slug: entry.slug,
13
+ theming: entry.theming.as_json(only: [:privacy_link_url]),
13
14
  page_types: PageTypesSeed.new(config).as_json,
14
15
  file_url_templates: FileUrlTemplatesSeed.new(config).as_json,
15
16
  file_model_types: config.file_types
@@ -27,11 +27,29 @@ module Pageflow
27
27
 
28
28
  def entry_global_links(entry)
29
29
  links = []
30
+
30
31
  if entry.theming.imprint_link_label.present? && entry.theming.imprint_link_url.present?
31
- links << link_to(raw(entry.theming.imprint_link_label), entry.theming.imprint_link_url, :target => '_blank', :tabindex => 2, :class => 'legal')
32
+ links << link_to(raw(entry.theming.imprint_link_label),
33
+ entry.theming.imprint_link_url,
34
+ target: '_blank',
35
+ tabindex: 2,
36
+ class: 'legal')
32
37
  end
38
+
33
39
  if entry.theming.copyright_link_label.present? && entry.theming.copyright_link_url.present?
34
- links << link_to(raw(entry.theming.copyright_link_label), entry.theming.copyright_link_url, :target => '_blank', :tabindex => 2, :class => 'copy')
40
+ links << link_to(raw(entry.theming.copyright_link_label),
41
+ entry.theming.copyright_link_url,
42
+ target: '_blank',
43
+ tabindex: 2,
44
+ class: 'copy')
45
+ end
46
+
47
+ if entry.theming.privacy_link_url.present?
48
+ links << link_to(I18n.t('pageflow.public.privacy_notice'),
49
+ "#{entry.theming.privacy_link_url}?lang=#{entry.locale}",
50
+ target: '_blank',
51
+ tabindex: 2,
52
+ class: 'privacy')
35
53
  end
36
54
 
37
55
  if links.any?
@@ -11,7 +11,7 @@ module Pageflow
11
11
  entry: entry)).html_safe
12
12
  end
13
13
 
14
- def entry_theming_seed(entry)
14
+ def entry_theme_seed(entry)
15
15
  theme = entry.theme
16
16
  {
17
17
  change_to_parent_page_at_storyline_boundary: theme.change_to_parent_page_at_storyline_boundary?,
@@ -7,11 +7,16 @@ module Pageflow
7
7
  end
8
8
 
9
9
  def public_i18n_translations(entry)
10
+ merge_ignoring_nil = lambda do |_, fallback, value|
11
+ value.presence || fallback
12
+ end
13
+
10
14
  {
11
15
  pageflow: {
12
16
  public: I18n.t('pageflow.public', locale: I18n.default_locale)
13
17
  .dup
14
- .deep_merge(I18n.t('pageflow.public', locale: entry.locale))
18
+ .deep_merge(I18n.t('pageflow.public', locale: entry.locale),
19
+ &merge_ignoring_nil)
15
20
  }
16
21
  }
17
22
  end
@@ -34,6 +34,7 @@
34
34
  <%= theming.input :imprint_link_url %>
35
35
  <%= theming.input :copyright_link_label %>
36
36
  <%= theming.input :copyright_link_url %>
37
+ <%= theming.input :privacy_link_url %>
37
38
 
38
39
  <% account_config.admin_form_inputs.find_all_for(:theming).each do |form_input| %>
39
40
  <%= form_input.build(theming) %>
@@ -0,0 +1,16 @@
1
+ <div id="login">
2
+ <h2><%= render_or_call_method_or_proc_on(self, active_admin_application.site_title) %> - <%= title t('pageflow.initial_password.title') %></h2>
3
+
4
+ <%= devise_error_messages! %>
5
+ <%= active_admin_form_for(resource, as: resource_name, url: admin_initial_password_path, html: { method: :put }) do |f|
6
+ f.inputs do
7
+ f.input :password
8
+ f.input :password_confirmation
9
+ f.input :reset_password_token, as: :hidden, input_html: { value: resource.reset_password_token }
10
+ end
11
+ f.actions do
12
+ f.action :submit, label: t('pageflow.initial_password.submit'), button_html: { value: t('pageflow.initial_password.submit') }
13
+ end
14
+ end
15
+ %>
16
+ </div>
@@ -1,6 +1,6 @@
1
1
  json.merge! common_entry_seed(entry)
2
2
 
3
- json.theming entry_theming_seed(entry)
3
+ json.theme entry_theme_seed(entry)
4
4
 
5
5
  json.storylines entry_storylines_seed(entry)
6
6
  json.chapters entry_chapters_seed(entry)
@@ -2,8 +2,8 @@
2
2
 
3
3
  <p><%= t('.instruction') %></p>
4
4
 
5
- <p><%= link_to main_app.edit_user_password_url(@user, reset_password_token: @password_token),
6
- main_app.edit_user_password_url(@user, reset_password_token: @password_token) %></p>
5
+ <p><%= link_to edit_admin_initial_password_url(reset_password_token: @password_token),
6
+ edit_admin_initial_password_url(reset_password_token: @password_token) %></p>
7
7
 
8
8
  <p><%= t('.ending') %></p>
9
9
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  <%= t('.instruction') %>
4
4
 
5
- <%= main_app.edit_user_password_url(@user, reset_password_token: @password_token) %>
5
+ <%= edit_admin_initial_password_url(reset_password_token: @password_token) %>
6
6
 
7
7
  <%= t('.ending') %>
8
8
 
@@ -268,6 +268,7 @@ de:
268
268
  home_url: Redirect URL
269
269
  imprint_link_label: Impressum-Link Text
270
270
  imprint_link_url: Impressum-Link URL
271
+ privacy_link_url: Datenschutz-Link URL
271
272
  theme_name: Theme
272
273
  pageflow/video_file:
273
274
  dimensions: Maße
@@ -756,7 +757,6 @@ de:
756
757
  no_entries: Keine Beiträge
757
758
  no_members: Keine Benutzer
758
759
  theming_defaults_inline_help: Die folgenden Einstellungen werden als Standard für neue Beiträge des Kontos verwendet. Änderungen wirken sich nicht auf existierende Beiträge aus.
759
- widgets_inline_help: DELETED
760
760
  entries:
761
761
  add_folder: Ordner hinzufügen
762
762
  confirm_depublish: Soll der Beitrag wirklich depubliziert werden?
@@ -983,6 +983,8 @@ de:
983
983
  captions: Ton-Ersatz-Textspur
984
984
  descriptions: Bild-Ersatz-Textspur
985
985
  subtitles: Übersetzungs-Textspur
986
+ cookie_notice_bar:
987
+ widget_type_name: Leiste am unteren Seitenrand
986
988
  default_theme:
987
989
  name: Standard
988
990
  delayed_text_fade_in:
@@ -1017,6 +1019,8 @@ de:
1017
1019
  header: Dies ist ein leerer Pageflow
1018
1020
  intro: Pageflows bestehen aus Kapiteln und Seiten. In der Seitenleiste rechts findest Du die Gliederung.
1019
1021
  outro: Hier siehst Du dann eine Vorschau deines Pageflows.
1022
+ cookie_notice_bar:
1023
+ widget_type_info_box_text: Der Hinweis wird beim ersten Öffnen von Beiträgen, die Cookies verwenden, am unteren Seitenrand angezeigt. Cookies werden gesetzt, falls ein Dienst zur Erhebnung von Nutzungsdaten (z.B. Google Analytics) verwendet wird oder wenn die Funktion 'Neue Seiten hervorheben' aktiv ist.
1020
1024
  entries:
1021
1025
  unsupported_browser_hint:
1022
1026
  message: Die Version Ihres Browsers wird vom Pageflow Editor nicht unterstützt. Bitte benutzen Sie einen aktuellen Browser.
@@ -1216,10 +1220,6 @@ de:
1216
1220
  title: Hilfe
1217
1221
  help_button:
1218
1222
  open_help: Hilfe anzeigen
1219
- inputs:
1220
- reference:
1221
- edit: DELETED
1222
- reset: DELETED
1223
1223
  list_blank_slate:
1224
1224
  text: "(Keine Einträge)"
1225
1225
  list_item:
@@ -1652,6 +1652,9 @@ de:
1652
1652
  image_rights: Bildrechte
1653
1653
  highdef_video_encoding:
1654
1654
  feature_name: Full HD und 4K Videos
1655
+ initial_password:
1656
+ submit: Speichern und anmelden
1657
+ title: Passwort festlegen
1655
1658
  invalid_transition: Der gewünschte Statuswechsel ist nicht erlaubt.
1656
1659
  languages:
1657
1660
  ar: Arabisch
@@ -1718,6 +1721,7 @@ de:
1718
1721
  ui:
1719
1722
  configuration_editor:
1720
1723
  tabs:
1724
+ cookie_notice_bar: Cookie Hinweis
1721
1725
  files: Dateien
1722
1726
  general: Allgemein
1723
1727
  links: Verweise
@@ -1763,7 +1767,7 @@ de:
1763
1767
 
1764
1768
  Alle Änderungen sind in der linken Frontend-Ansicht sichtbar, sobald das entsprechende Texteingabefeld im Editierbereich verlassen wurde.
1765
1769
  thumbnail_image_id: Das Thumbnail ersetzt das ansonsten automatisch generierte Vorschaubild in der Navigation und in der Übersicht.
1766
- transition: Wähle hier den Effekt, der beim Erreichen dieser der Seite benutzt werden soll.
1770
+ transition: Wähle hier den Effekt, der beim Erreichen dieser Seite benutzt werden soll.
1767
1771
  templates:
1768
1772
  inputs:
1769
1773
  file_input:
@@ -1803,6 +1807,8 @@ de:
1803
1807
  url_hint: URL muss mit http:// beginnen.
1804
1808
  url_hint_https: URL muss mit http:// oder https:// beginnen.
1805
1809
  unauthorized: Sie sind nicht berechtigt diese Seite anzuzeigen.
1810
+ unmute_button:
1811
+ widget_type_name: "'Ton ein'-Button"
1806
1812
  user_mailer:
1807
1813
  invitation:
1808
1814
  ending: Dankeschön und viel Spaß,
@@ -1825,6 +1831,8 @@ de:
1825
1831
  none: "(Kein)"
1826
1832
  roles:
1827
1833
  analytics: Zählpixel
1834
+ background_media_control: Hintergrund-Media-Control
1835
+ cookie_notice: Cookie Hinweis
1828
1836
  mobile_navigation: Mobile Navigation
1829
1837
  navigation: Navigationsleiste
1830
1838
  player_controls: Player Controls
@@ -268,6 +268,7 @@ en:
268
268
  home_url: Redirect URL
269
269
  imprint_link_label: Legal notice link label
270
270
  imprint_link_url: Legal notice link URL
271
+ privacy_link_url: Privacy link URL
271
272
  theme_name: Theme
272
273
  pageflow/video_file:
273
274
  dimensions: Dimensions
@@ -756,13 +757,12 @@ en:
756
757
  no_entries: No Stories
757
758
  no_members: No members
758
759
  theming_defaults_inline_help: The following settings will be used as defaults for new entries in this account. Changes do not affect existing entries.
759
- widgets_inline_help: DELETED
760
760
  entries:
761
761
  add_folder: Add folder
762
- confirm_depublish: Unpublish this story?
762
+ confirm_depublish: Depublish this story?
763
763
  confirm_duplicate: Duplicate this story?
764
764
  confirm_restore: Restore story to the selected version? A snapshot will be created, so that you can roll back later.
765
- depublish: Unpublish story
765
+ depublish: Depublish story
766
766
  duplicate: Copy Story
767
767
  edit: Edit
768
768
  edit_config: Edit configuration
@@ -981,6 +981,8 @@ en:
981
981
  captions: Audio Replacement Text Track
982
982
  descriptions: Image Description Text Track
983
983
  subtitles: Translation Text Track
984
+ cookie_notice_bar:
985
+ widget_type_name: Bar at lower page margin
984
986
  default_theme:
985
987
  name: Default
986
988
  delayed_text_fade_in:
@@ -1017,6 +1019,8 @@ en:
1017
1019
  header: This is an empty Pageflow
1018
1020
  intro: Each Pageflow consist of chapters and pages. The editor panel to your right shows the outline of your story.
1019
1021
  outro: In this area, a live preview will be shown.
1022
+ cookie_notice_bar:
1023
+ widget_type_info_box_text: The notice is displayed when visiting an entry that uses Cookies. Cookies are set, if an analytics integration is active or the 'Emphasize new pages' feature has been enabled.
1020
1024
  entries:
1021
1025
  unsupported_browser_hint:
1022
1026
  message: The Pageflow editor does not support the browser version you are using. We recommend upgrading your browser to the most recent version.
@@ -1216,10 +1220,6 @@ en:
1216
1220
  title: Help
1217
1221
  help_button:
1218
1222
  open_help: Open help
1219
- inputs:
1220
- reference:
1221
- edit: DELETED
1222
- reset: DELETED
1223
1223
  list_blank_slate:
1224
1224
  text: "(No items)"
1225
1225
  list_item:
@@ -1559,7 +1559,7 @@ en:
1559
1559
  click on a page type on the left column.
1560
1560
  publishing:
1561
1561
  menu_item: Publishing
1562
- text: "# Publishing\n\nClick on the „Publish“ button to publish your Pageflow. A\nPageflow can be published at any point in time. \n\nBesides this, there is the possibility to publish Pageflows with \na password protection. Just activate „Protect with password“. \nPageflow uses the name of the account as user name and \nautomatically generates a password, which can be changed afterwords. \n\nAlready published stories can also be protected with a password by \npublishing another version - the other way round works as well. \nFor this please just click on the „Publishing“ button. The following \nsteps will then be shown and explained within the dialog window.\n\nPublished\nPageflows can also be unpublished manually at any time. If a\nPageflow has already been published, changes will not be\nshown until you publish your report again. A Pageflow can\nonly be edited by one author at one time.\n\nThumbnail for embedding the Pageflow on external websites\nAfter publishing your Pageflow, the system generates a\nthumbnail, which can be embedded as an iframe to the\nhyperlink of another website. By means of the code, the size\nof the iframe can be adjusted. The minimum size is a height\n150px and a width of 220px."
1562
+ text: "# Publishing\n\nClick on the „Publish“ button to publish your Pageflow. A\nPageflow can be published at any point in time. \n\nBesides this, there is the possibility to publish Pageflows with \na password protection. Just activate „Protect with password“. \nPageflow uses the name of the account as user name and \nautomatically generates a password, which can be changed afterwords. \n\nAlready published stories can also be protected with a password by \npublishing another version - the other way round works as well. \nFor this please just click on the „Publishing“ button. The following \nsteps will then be shown and explained within the dialog window.\n\nPublished\nPageflows can also be depublished manually at any time. If a\nPageflow has already been published, changes will not be\nshown until you publish your report again. A Pageflow can\nonly be edited by one author at one time.\n\nThumbnail for embedding the Pageflow on external websites\nAfter publishing your Pageflow, the system generates a\nthumbnail, which can be embedded as an iframe to the\nhyperlink of another website. By means of the code, the size\nof the iframe can be adjusted. The minimum size is a height\n150px and a width of 220px."
1563
1563
  storylines:
1564
1564
  menu_item: Storylines
1565
1565
  text: "# Storylines\n\nIn addition to linear narrations from top to bottom, further storylines can be used as non linear excursions to enlarge upon parts of a story. A „Storyline“ consists of chapters and pages, which users scroll through. Distinguishable is the so called main storyline from further subordinate ones. The user starts off in the main storyline and can then navigate to different excursions.\n\nTo create such an excursion from the main storyline use one of the „Link“ pages (Mosaic, Collage, Hotspot) and create the desired connections via „Links“.\n\nBy choosing a „Parent page“ you can determine to which page users return after scrolling the storyline´s last page.\n\nTransitions to subordinate storylines are visually supported by a horizontal animation as the default setting for entering and leaving. \n\nStorylines are created by clicking onto the plus button next to the storyline menu. Thereby a new chapter opens automatically in which further pages can be added. \n\n\n### Parent pages\n\nIn every subordinate storyline a „Parent page“ must be determined to define where users land when scrolling back.\n\nThis target page can be chosen by clicking on the pen symbol next to the storyline menu. When a „Parent page“ is defined, a back button will be shown automatically in the subordinate storyline that will always lead users back to the „Parent page“.\n\n\n### Chapter hierarchy\n\nThe use of more than one „subordinary“ storylines and the definition of „Parent pages“ leads to a chapter hierarchy as known from books:\n\n1. (Main storyline), 1.1. (Superior sub storyline), 1.1.1. (Subordinate sub storyline) and so on.\n\nThis hierarchy can be edited afterwards by clicking on the pen symbol next to the storyline menu. A subordinate storyline can be turned into the main storyline as well.\n\n\n### Scroll successors\n\nIf you want to lead users from a last page of a subordinate chapter (1.1.1) to another position than\nthe „Parent page“ (e.g. to 2.1) you can define a „Scroll successor“. Therefore click on the blue pen symbol within the storyline settings. As an example the following sequence might be imaginable: 1 -> 1.1 -> 1.1.1 -> 2.1\n\nAttention: While editing only the selected chapter will be shown in the sidebar. To switch to the other chapters click onto the storyline menu."
@@ -1624,6 +1624,9 @@ en:
1624
1624
  image_rights: Credits
1625
1625
  highdef_video_encoding:
1626
1626
  feature_name: Full HD and 4K videos
1627
+ initial_password:
1628
+ submit: Save and sign in
1629
+ title: Set your password
1627
1630
  invalid_transition: Invalid transition
1628
1631
  languages:
1629
1632
  ar: Arabic
@@ -1690,6 +1693,7 @@ en:
1690
1693
  ui:
1691
1694
  configuration_editor:
1692
1695
  tabs:
1696
+ cookie_notice_bar: Cookie Notice
1693
1697
  files: Files
1694
1698
  general: General
1695
1699
  links: Links
@@ -1778,6 +1782,8 @@ en:
1778
1782
  url_hint: 'URL must start with http:// '
1779
1783
  url_hint_https: URL must start with http:// or https://
1780
1784
  unauthorized: You are not authorized to view this page.
1785
+ unmute_button:
1786
+ widget_type_name: Unmute button
1781
1787
  user_mailer:
1782
1788
  invitation:
1783
1789
  ending: Thank you and have fun,
@@ -1800,6 +1806,8 @@ en:
1800
1806
  none: "(none)"
1801
1807
  roles:
1802
1808
  analytics: Tracking
1809
+ background_media_control: Background media control
1810
+ cookie_notice: Cookie Notice
1803
1811
  mobile_navigation: Mobile navigation
1804
1812
  navigation: Navigation bar
1805
1813
  player_controls: Player Controls
data/config/routes.rb CHANGED
@@ -30,6 +30,12 @@ Pageflow::Engine.routes.draw do
30
30
  resource :edit_lock
31
31
  end
32
32
 
33
+ namespace :admin do
34
+ devise_scope :user do
35
+ resource :initial_password, only: [:edit, :update]
36
+ end
37
+ end
38
+
33
39
  namespace :editor do
34
40
  resources :entries, :only => :index, :shallow => true do
35
41
  get :seed, :on => :member
@@ -0,0 +1,5 @@
1
+ class AddPrivacyLinkUrlToThemings < ActiveRecord::Migration
2
+ def change
3
+ add_column :pageflow_themings, :privacy_link_url, :string
4
+ end
5
+ end
@@ -31,5 +31,13 @@ module Pageflow
31
31
  def self.slim_player_controls
32
32
  new('slim_player_controls', ['player_controls'], 'pageflow/widgets/placeholder')
33
33
  end
34
+
35
+ def self.cookie_notice_bar
36
+ Pageflow::React.create_widget_type('cookie_notice_bar', 'cookie_notice')
37
+ end
38
+
39
+ def self.unmute_button
40
+ Pageflow::React.create_widget_type('unmute_button', 'background_media_control')
41
+ end
34
42
  end
35
43
  end
@@ -5,6 +5,8 @@ module Pageflow
5
5
  config.widget_types.register(Pageflow::BuiltInWidgetType.mobile_navigation, default: true)
6
6
  config.widget_types.register(Pageflow::BuiltInWidgetType.slim_player_controls)
7
7
  config.widget_types.register(Pageflow::BuiltInWidgetType.classic_player_controls, default: true)
8
+ config.widget_types.register(Pageflow::BuiltInWidgetType.cookie_notice_bar)
9
+ config.widget_types.register(Pageflow::BuiltInWidgetType.unmute_button, default: true)
8
10
  end
9
11
  end
10
12
  end
@@ -1,3 +1,3 @@
1
1
  module Pageflow
2
- VERSION = '12.2.0'.freeze
2
+ VERSION = '12.3.0'.freeze
3
3
  end
@@ -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
+ }));