1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-09-20 09:18:48 +02:00

Implement macros as audio sources

Summary:
Fixes T3887. Basically:

  - Macros with audio get passed to the `audio-source` behavior.
  - This keeps track of where they are relative to the viewport as the user scrolls.
    - When the user scrolls a "once" macro into view, and it reaches roughly the middle of the screen, we play the sound.
    - When the user scrolls near a "loop" macro, we start playing the sound at low volume and increase the volume as the user scrolls.

This feels pretty good on both counts.

Test Plan: Tested in Safari, Chrome, and Firefox. FF seems a bit less responsive and doesn't support MP3, but it was fairly nice in Chrome/Safari.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T3887

Differential Revision: https://secure.phabricator.com/D7160
This commit is contained in:
epriestley 2013-09-27 16:02:02 -07:00
parent 7091dad606
commit 9c432545e4
3 changed files with 170 additions and 2 deletions

View file

@ -1326,6 +1326,19 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/js/core/behavior-more.js',
),
'javelin-behavior-audio-source' =>
array(
'uri' => '/res/21831141/rsrc/js/core/behavior-audio-source.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-vector',
3 => 'javelin-dom',
),
'disk' => '/rsrc/js/core/behavior-audio-source.js',
),
'javelin-behavior-audit-preview' =>
array(
'uri' => '/res/d8f31e46/rsrc/js/application/diffusion/behavior-audit-preview.js',

View file

@ -25,14 +25,28 @@ final class PhabricatorRemarkupRuleImageMacro
->withStatus(PhabricatorMacroQuery::STATUS_ACTIVE)
->execute();
foreach ($rows as $row) {
$this->images[$row->getName()] = $row->getFilePHID();
$spec = array(
'image' => $row->getFilePHID(),
);
$behavior_none = PhabricatorFileImageMacro::AUDIO_BEHAVIOR_NONE;
if ($row->getAudioPHID()) {
if ($row->getAudioBehavior() != $behavior_none) {
$spec += array(
'audio' => $row->getAudioPHID(),
'audioBehavior' => $row->getAudioBehavior(),
);
}
}
$this->images[$row->getName()] = $spec;
}
}
$name = (string)$matches[1];
if (array_key_exists($name, $this->images)) {
$phid = $this->images[$name];
$phid = $this->images[$name]['image'];
$file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $phid);
@ -58,14 +72,42 @@ final class PhabricatorRemarkupRuleImageMacro
}
}
$id = null;
$audio_phid = idx($this->images[$name], 'audio');
if ($audio_phid) {
$id = celerity_generate_unique_node_id();
$loop = null;
switch (idx($this->images[$name], 'audioBehavior')) {
case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP:
$loop = true;
break;
}
$file = id(new PhabricatorFile())->loadOneWhere(
'phid = %s',
$audio_phid);
if ($file) {
Javelin::initBehavior(
'audio-source',
array(
'sourceID' => $id,
'audioURI' => $file->getBestURI(),
'loop' => $loop,
));
}
}
$img = phutil_tag(
'img',
array(
'id' => $id,
'src' => $src_uri,
'alt' => $matches[1],
'title' => $matches[1],
'style' => $style,
));
return $this->getEngine()->storeText($img);
} else {
return $matches[1];

View file

@ -0,0 +1,113 @@
/**
* @provides javelin-behavior-audio-source
* @requires javelin-behavior
* javelin-stratcom
* javelin-vector
* javelin-dom
* @javelin
*/
/**
* Allows an element to behave as an audio source. It plays a sound either
* when the user scrolls it into view, or loops a sound which gets louder and
* louder as the user gets closer.
*/
JX.behavior('audio-source', function(config, statics) {
if (!window.Audio) {
return;
}
var audio = new Audio();
audio.setAttribute('src', config.audioURI);
if (config.loop) {
audio.setAttribute('loop', true);
}
audio.load();
config.audio = audio;
statics.items = statics.items || [];
statics.items.push(config);
if (statics.initialized) {
return;
}
statics.initialized = true;
var onupdate = function() {
timeout = null;
var scroll = JX.Vector.getScroll();
var view = JX.Vector.getViewport();
var view_mid = scroll.y + (view.y / 2);
for (var ii = 0; ii < statics.items.length; ii++) {
var item = statics.items[ii];
if (!item.element) {
try {
item.element = JX.$(item.sourceID);
} catch (ignored) {
continue;
}
}
var pos = JX.Vector.getPos(statics.items[ii].element);
var dim = JX.Vector.getDim(statics.items[ii].element);
var item_mid = pos.y + (dim.y / 2);
var item_distance = Math.abs(item_mid - view_mid);
// item_distance is the number of pixels between the vertical middle
// of the macro and the vertical middle of the viewport. We divide it
// by the viewport height to get the "number of viewports" away from
// the middle we are, then map that to [0, 1], where 0 means that the
// image is far away from the viewport and 1 means the image is pretty
// much in the middle of the viewport.
var near = 1.25 - ((item_distance / view.y) * 1.25);
near = Math.max(0, near);
near = Math.min(1, near);
if (near === 0) {
if (item.playing) {
item.audio.pause();
item.playing = false;
// If this isn't an ambient/looping sound, it only gets to fire
// once. Even if it didn't finish, throw it out.
if (!item.loop) {
statics.items.splice(ii, 1);
ii--;
}
}
continue;
} else {
if (!item.playing) {
if (!item.loop && near < 1) {
// Don't start playing one-shot items until they're solidly on
// screen.
continue;
}
item.audio.volume = near;
item.playing = true;
item.audio.play();
} else {
item.audio.volume = near;
}
}
}
};
var timeout;
var onadjust = function() {
timeout && clearTimeout(timeout);
timeout = setTimeout(onupdate, 200);
};
JX.Stratcom.listen(['scroll', 'resize'], null, onadjust);
onadjust();
});