mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 23:02:42 +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',
|
'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' =>
|
'javelin-behavior-audit-preview' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/d8f31e46/rsrc/js/application/diffusion/behavior-audit-preview.js',
|
'uri' => '/res/d8f31e46/rsrc/js/application/diffusion/behavior-audit-preview.js',
|
||||||
|
|
|
@ -25,14 +25,28 @@ final class PhabricatorRemarkupRuleImageMacro
|
||||||
->withStatus(PhabricatorMacroQuery::STATUS_ACTIVE)
|
->withStatus(PhabricatorMacroQuery::STATUS_ACTIVE)
|
||||||
->execute();
|
->execute();
|
||||||
foreach ($rows as $row) {
|
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];
|
$name = (string)$matches[1];
|
||||||
|
|
||||||
if (array_key_exists($name, $this->images)) {
|
if (array_key_exists($name, $this->images)) {
|
||||||
$phid = $this->images[$name];
|
$phid = $this->images[$name]['image'];
|
||||||
|
|
||||||
$file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $phid);
|
$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 = phutil_tag(
|
||||||
'img',
|
'img',
|
||||||
array(
|
array(
|
||||||
|
'id' => $id,
|
||||||
'src' => $src_uri,
|
'src' => $src_uri,
|
||||||
'alt' => $matches[1],
|
'alt' => $matches[1],
|
||||||
'title' => $matches[1],
|
'title' => $matches[1],
|
||||||
'style' => $style,
|
'style' => $style,
|
||||||
));
|
));
|
||||||
|
|
||||||
return $this->getEngine()->storeText($img);
|
return $this->getEngine()->storeText($img);
|
||||||
} else {
|
} else {
|
||||||
return $matches[1];
|
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