mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 08:52:39 +01: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:
parent
7091dad606
commit
9c432545e4
3 changed files with 170 additions and 2 deletions
|
@ -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',
|
||||
|
|
|
@ -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];
|
||||
|
|
113
webroot/rsrc/js/core/behavior-audio-source.js
Normal file
113
webroot/rsrc/js/core/behavior-audio-source.js
Normal 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();
|
||||
|
||||
});
|
Loading…
Reference in a new issue