Sebastian Berlin (WMSE) has uploaded a new change for review.
https://gerrit.wikimedia.org/r/315218
Change subject: Skip back word
......................................................................
Skip back word
Enabled skipping back one word at a time while playing. Button and keyborad
shortcut (ctrl + up arrow by deafult) was added.
Also changed from spies to stubs in tests to isolate them further.
Bug: T133687
Change-Id: I0165d2c55680191160ec1f717f5b5452172aecd9
---
M extension.json
M modules/ext.wikispeech.css
M modules/ext.wikispeech.js
M tests/qunit/ext.wikispeech.test.js
4 files changed, 244 insertions(+), 56 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Wikispeech
refs/changes/18/315218/1
diff --git a/extension.json b/extension.json
index 6f0c748..334d4f6 100644
--- a/extension.json
+++ b/extension.json
@@ -87,6 +87,10 @@
"skipAheadWord": {
"key": 40,
"modifiers": [ "ctrl" ]
+ },
+ "skipBackWord": {
+ "key": 38,
+ "modifiers": [ "ctrl" ]
}
},
"WikispeechSkipBackRewindsThreshold": 3.0
diff --git a/modules/ext.wikispeech.css b/modules/ext.wikispeech.css
index d362b04..18f6934 100644
--- a/modules/ext.wikispeech.css
+++ b/modules/ext.wikispeech.css
@@ -21,3 +21,7 @@
.ext-wikispeech-skip-ahead-word:after {
content: "Skip ahead word";
}
+
+.ext-wikispeech-skip-back-word:after {
+ content: "Skip back word";
+}
diff --git a/modules/ext.wikispeech.js b/modules/ext.wikispeech.js
index bedfd7a..81ba3fa 100644
--- a/modules/ext.wikispeech.js
+++ b/modules/ext.wikispeech.js
@@ -30,6 +30,11 @@
'ext-wikispeech-skip-ahead-word',
self.skipAheadToken
);
+ self.addButton(
+ 'ext-wikispeech-skip-back-word-button',
+ 'ext-wikispeech-skip-back-word',
+ self.skipBackToken
+ );
};
this.addButton = function ( id, cssClass, onClickFunction ) {
@@ -232,6 +237,74 @@
};
/**
+ * Skip to the previous token.
+ */
+
+ this.skipBackToken = function () {
+ var $previousToken, $utterance, $audio;
+
+ if ( self.isPlaying() ) {
+ $previousToken =
+ self.getPreviousToken(
self.getCurrentToken() );
+ $utterance = $previousToken.parent().parent();
+ if ( $utterance.get( 0 ) !==
$currentUtterance.get( 0 ) ) {
+ self.playUtterance( $utterance );
+ }
+ $audio = $currentUtterance.children( 'audio' );
+ $audio.prop( 'currentTime',
$previousToken.attr( 'time' ) );
+ }
+ };
+
+ /**
+ * Get the token before a given token.
+ *
+ * Tokens that are "silent" i.e. have a duration of zero or
have no
+ * transcription, are ignored.
+ *
+ * @param $token Original token.
+ * @return The token before $token.
+ */
+
+ this.getPreviousToken = function ( $token ) {
+ var $utterance, $followingToken, $tokens;
+
+ $utterance = $token.parent().parent();
+ do {
+ $followingToken = $token;
+ $token = $token.prev();
+ if ( !$token.length ) {
+ $utterance = $utterance.prev();
+ if ( !$utterance.length ) {
+ return $();
+ }
+ $tokens = $utterance.find( 'token' );
+ $token = $( $utterance.find( 'token' )
+ .get(
$tokens.length - 1 ) );
+ }
+ // Ignore tokens that either have a duration of
zero or that
+ // have no text.
+ } while (
+ $token.attr( 'time' ) === $followingToken.attr(
'time' ) ||
+ $token.text() === ''
+ );
+ return $token;
+ };
+
+ /**
+ * Get the token element for the token currently playing.
+ *
+ * @return The token currently playing.
+ */
+
+ this.getCurrentToken = function () {
+ var index, $tokens;
+
+ index = self.getCurrentTokenIndex();
+ $tokens = $currentUtterance.find( $( 'token' ) );
+ return $( $tokens.get( index ) );
+ };
+
+ /**
* Register listeners for keyboard shortcuts.
*/
@@ -256,6 +329,11 @@
self.eventMatchShortcut( event,
shortcuts.skipAheadWord )
) {
self.skipAheadToken();
+ return false;
+ } else if (
+ self.eventMatchShortcut( event,
shortcuts.skipBackWord )
+ ) {
+ self.skipBackToken();
return false;
}
} );
@@ -310,7 +388,6 @@
$nextUtteranceAudio =
$nextUtterance.children( 'audio' );
$audio.on( {
play: function () {
- $currentUtterance =
$utterance;
self.prepareUtterance(
$nextUtterance );
},
ended: function () {
@@ -337,7 +414,7 @@
*
* @param $utterance The original utterance.
* @param {number} offest The difference, in index, to the
wanted
- * utterance. Can be negative for preceeding utterances.
+ * utterance. Can be negative for preceding utterances.
* @return The utterance after the original utterance. Empty
object if
* $utterance isn't a valid utterance or if an uttrance
couldn't be
* found.
diff --git a/tests/qunit/ext.wikispeech.test.js
b/tests/qunit/ext.wikispeech.test.js
index b416d7b..4136379 100644
--- a/tests/qunit/ext.wikispeech.test.js
+++ b/tests/qunit/ext.wikispeech.test.js
@@ -23,30 +23,6 @@
$( '#qunit-fixture' ).append(
$( '<h1></h1>' ).attr( 'id', 'firstHeading' )
);
- mw.config.set(
- 'wgWikispeechKeyboardShortcuts', {
- playStop: {
- key: 32,
- modifiers: [ 'ctrl' ]
- },
- skipAheadSentence: {
- key: 39,
- modifiers: [ 'ctrl' ]
- },
- skipBackSentence: {
- key: 37,
- modifiers: [ 'ctrl' ]
- },
- skipAheadWord: {
- key: 40,
- modifiers: [ 'ctrl' ]
- }
- }
- );
- mw.config.set(
- 'wgWikispeechSkipBackRewindsThreshold',
- 3.0
- );
},
teardown: function () {
server.restore();
@@ -72,7 +48,7 @@
QUnit.test( 'prepareUtterance()', function ( assert ) {
assert.expect( 1 );
- sinon.spy( wikispeech, 'loadAudio' );
+ sinon.stub( wikispeech, 'loadAudio', function () {} );
wikispeech.prepareUtterance( $( '#utterance-0' ) );
@@ -86,7 +62,7 @@
QUnit.test( "prepareUtterance(): don't request if already requested",
function ( assert ) {
// jscs:enable validateQuoteMarks
assert.expect( 1 );
- sinon.spy( wikispeech, 'loadAudio' );
+ sinon.stub( wikispeech, 'loadAudio', function () {} );
$( '#utterance-0' ).prop( 'requested', true );
wikispeech.prepareUtterance( $( '#utterance-0' ) );
@@ -98,7 +74,7 @@
var $nextUtterance = $( '#utterance-1' );
assert.expect( 1 );
wikispeech.prepareUtterance( $( '#utterance-0' ) );
- sinon.spy( wikispeech, 'prepareUtterance' );
+ sinon.stub( wikispeech, 'prepareUtterance', function () {} );
$( '#utterance-0 audio' ).trigger( 'play' );
@@ -112,7 +88,7 @@
QUnit.test( "prepareUtterance(): don't prepare next audio if it doesn't
exist", function ( assert ) {
// jscs:enable validateQuoteMarks
assert.expect( 1 );
- sinon.spy( wikispeech, 'prepareUtterance' );
+ sinon.stub( wikispeech, 'prepareUtterance', function () {} );
wikispeech.prepareUtterance( $( '#utterance-1' ) );
$( '#utterance-1 audio' ).trigger( 'play' );
@@ -129,7 +105,7 @@
wikispeech.prepareUtterance( $( '#utterance-0' ) );
wikispeech.prepareUtterance( $( '#utterance-1' ) );
$nextAudio = $( '#utterance-1' ).children( 'audio' ).get( 0 );
- sinon.spy( $nextAudio, 'play' );
+ sinon.stub( $nextAudio, 'play', function () {} );
wikispeech.playUtterance( $( '#utterance-0' ) );
$( '#utterance-0 audio' ).trigger( 'ended' );
@@ -140,7 +116,7 @@
QUnit.test( 'prepareUtterance(): stop when end of text is reached',
function ( assert ) {
var $lastUtterance = $( '#utterance-1' );
assert.expect( 1 );
- sinon.spy( wikispeech, 'stop' );
+ sinon.stub( wikispeech, 'stop', function () {} );
wikispeech.prepareUtterance( $lastUtterance );
wikispeech.playUtterance( $lastUtterance );
@@ -151,7 +127,7 @@
QUnit.test( 'loadAudio()', function ( assert ) {
assert.expect( 4 );
- sinon.spy( wikispeech, 'addTokenElements' );
+ sinon.stub( wikispeech, 'addTokenElements', function () {} );
wikispeech.loadAudio( $( '#utterance-0' ) );
@@ -178,7 +154,8 @@
} );
QUnit.test( 'addButtons()', function ( assert ) {
- assert.expect( 4 );
+ assert.expect( 5 );
+
wikispeech.addButtons();
assert.strictEqual(
@@ -195,6 +172,10 @@
);
assert.strictEqual(
$( '#firstHeading
#ext-wikispeech-skip-ahead-word-button' ).length,
+ 1
+ );
+ assert.strictEqual(
+ $( '#firstHeading
#ext-wikispeech-skip-back-word-button' ).length,
1
);
} );
@@ -216,24 +197,13 @@
*/
function testClickButton( assert, functionName, buttonId ) {
- var baseFunction, called;
-
assert.expect( 1 );
- // Sinon has problems spying on the on click functions. It
seems to
- // be caused by function names getting lost at some point. This
- // replaces the tested function and just sets a boolean if it's
called.
- baseFunction = wikispeech[ functionName ];
- called = false;
- wikispeech[ functionName ] = function () {
- called = true;
- };
+ sinon.stub( wikispeech, functionName, function () {} );
wikispeech.addButtons();
$( buttonId ).click();
- assert.strictEqual( called, true );
- // Restore the tested function.
- wikispeech[ functionName ] = baseFunction;
+ assert.strictEqual( wikispeech[ functionName ].called, true );
}
QUnit.test( 'Clicking skip ahead sentence button', function ( assert ) {
@@ -262,7 +232,7 @@
QUnit.test( 'playOrStop(): play', function ( assert ) {
assert.expect( 1 );
- sinon.spy( wikispeech, 'play' );
+ sinon.stub( wikispeech, 'play', function () {} );
wikispeech.playOrStop();
@@ -272,14 +242,14 @@
QUnit.test( 'playOrStop(): stop', function ( assert ) {
assert.expect( 1 );
wikispeech.play();
- sinon.spy( wikispeech, 'stop' );
+ sinon.stub( wikispeech, 'stop', function () {} );
wikispeech.playOrStop();
assert.strictEqual( wikispeech.stop.called, true );
} );
- QUnit.test( 'Pressing keyboard shortcut for playStop', function (
assert ) {
+ QUnit.test( 'Pressing keyboard shortcut for play/stop', function (
assert ) {
testKeyboardShortcut( assert, 'playOrStop', 32, 'c' );
} );
@@ -296,7 +266,7 @@
function testKeyboardShortcut( assert, functionName, keyCode, modifiers
) {
assert.expect( 1 );
- sinon.spy( wikispeech, functionName );
+ sinon.stub( wikispeech, functionName, function () {} );
wikispeech.addKeyboardShortcuts();
$( document ).trigger( createKeydownEvent( keyCode, modifiers )
);
@@ -314,16 +284,20 @@
* @return The created keydown event.
*/
- QUnit.test( 'Pressing keyboard shortcut for skipAheadSentence',
function ( assert ) {
+ QUnit.test( 'Pressing keyboard shortcut for skipping ahead sentence',
function ( assert ) {
testKeyboardShortcut( assert, 'skipAheadUtterance', 39, 'c' );
} );
- QUnit.test( 'Pressing keyboard shortcut for skipBackSentence', function
( assert ) {
+ QUnit.test( 'Pressing keyboard shortcut for skipping back sentence',
function ( assert ) {
testKeyboardShortcut( assert, 'skipBackUtterance', 37, 'c' );
} );
- QUnit.test( 'Pressing keyboard shortcut for skipAheadWord', function (
assert ) {
+ QUnit.test( 'Pressing keyboard shortcut for skipping ahead word',
function ( assert ) {
testKeyboardShortcut( assert, 'skipAheadToken', 40, 'c' );
+ } );
+
+ QUnit.test( 'Pressing keyboard shortcut for skipping back word',
function ( assert ) {
+ testKeyboardShortcut( assert, 'skipBackToken', 38, 'c' );
} );
function createKeydownEvent( keyCode, modifiers ) {
@@ -402,7 +376,7 @@
QUnit.test( 'skipAheadUtterance(): stop if no next utterance', function
( assert ) {
assert.expect( 1 );
- sinon.spy( wikispeech, 'stop' );
+ sinon.stub( wikispeech, 'stop', function () {} );
wikispeech.playUtterance( $( '#utterance-1' ) );
wikispeech.skipAheadUtterance();
@@ -776,10 +750,139 @@
);
wikispeech.play();
$( '#utterance-0 audio' ).prop( 'currentTime', 1.0 );
- sinon.spy( wikispeech, 'skipAheadUtterance' );
+ sinon.stub( wikispeech, 'skipAheadUtterance', function () {} );
wikispeech.skipAheadToken();
assert.strictEqual( wikispeech.skipAheadUtterance.called, true
);
} );
+
+ QUnit.test( 'skipBackToken()', function ( assert ) {
+ assert.expect( 1 );
+ wikispeech.prepareUtterance( $( '#utterance-0' ) );
+ $( '#utterance-0' ).append(
+ $( '<tokens></tokens>' )
+ .append(
+ $( '<token></token>' )
+ .attr( 'time', 0.0 )
+ .text( '1' )
+ )
+ .append(
+ $( '<token></token>' )
+ .attr( 'time', 1.0 )
+ .text( '2' )
+ )
+ );
+ wikispeech.play();
+ $( '#utterance-0 audio' ).prop( 'currentTime', 1.0 );
+
+ wikispeech.skipBackToken();
+
+ assert.strictEqual(
+ $( '#utterance-0 audio' ).prop( 'currentTime' ),
+ 0.0
+ );
+ } );
+
+ QUnit.test( 'skipBackToken(): skip to last token in previous utterance
if first token', function ( assert ) {
+ assert.expect( 2 );
+ wikispeech.prepareUtterance( $( '#utterance-0' ) );
+ wikispeech.prepareUtterance( $( '#utterance-1' ) );
+ $( '#utterance-0' ).append(
+ $( '<tokens></tokens>' )
+ .append(
+ $( '<token></token>' )
+ .attr( 'time', 0.0 )
+ .text( '1' )
+ )
+ .append(
+ $( '<token></token>' )
+ .attr( 'time', 1.0 )
+ .text( '2' )
+ )
+ );
+ $( '#utterance-1' ).append(
+ $( '<tokens></tokens>' )
+ .append(
+ $( '<token></token>' )
+ .attr( 'time', 0.0 )
+ )
+ );
+ wikispeech.playUtterance( $( '#utterance-1' ) );
+
+ wikispeech.skipBackToken();
+
+ assert.strictEqual(
+ $( '#utterance-0 audio' ).prop( 'paused' ),
+ false
+ );
+ assert.strictEqual(
+ $( '#utterance-0 audio' ).prop( 'currentTime' ),
+ 1.0
+ );
+ } );
+
+ QUnit.test( 'skipBackToken(): ignore empty tokens', function ( assert )
{
+ assert.expect( 1 );
+ wikispeech.prepareUtterance( $( '#utterance-0' ) );
+ $( '#utterance-0' ).append(
+ $( '<tokens></tokens>' )
+ .append(
+ $( '<token></token>' )
+ .attr( 'time', 0.0 )
+ .text( '1' )
+ )
+ .append(
+ $( '<token></token>' )
+ .attr( 'time', 1.0 )
+ .text( '' )
+ )
+ .append(
+ $( '<token></token>' )
+ .attr( 'time', 2.0 )
+ .text( '3' )
+ )
+ );
+ wikispeech.playUtterance( $( '#utterance-0' ) );
+ $( '#utterance-0 audio' ).prop( 'currentTime', 2.0 );
+
+ wikispeech.skipBackToken();
+
+ assert.strictEqual(
+ $( '#utterance-0 audio' ).prop( 'currentTime' ),
+ 0.0
+ );
+ } );
+
+ QUnit.test( 'skipBackToken(): ignore tokens with no length', function (
assert ) {
+ assert.expect( 1 );
+ wikispeech.prepareUtterance( $( '#utterance-0' ) );
+ $( '#utterance-0' ).append(
+ $( '<tokens></tokens>' )
+ .append(
+ $( '<token></token>' )
+ .attr( 'time', 0.0 )
+ .text( '1' )
+ )
+ .append(
+ $( '<token></token>' )
+ .attr( 'time', 1.0 )
+ .text( '2' )
+ )
+ .append(
+ $( '<token></token>' )
+ .attr( 'time', 1.0 )
+ .text( '3' )
+ )
+ );
+ wikispeech.playUtterance( $( '#utterance-0' ) );
+ $( '#utterance-0 audio' ).prop( 'currentTime', 1.0 );
+
+ wikispeech.skipBackToken();
+
+ assert.strictEqual(
+ $( '#utterance-0 audio' ).prop( 'currentTime' ),
+ 0.0
+ );
+ } );
} )( mediaWiki, jQuery );
--
To view, visit https://gerrit.wikimedia.org/r/315218
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I0165d2c55680191160ec1f717f5b5452172aecd9
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Wikispeech
Gerrit-Branch: master
Gerrit-Owner: Sebastian Berlin (WMSE) <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits