Sebastian Berlin (WMSE) has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/314664

Change subject: Skip back sentence
......................................................................

Skip back sentence

Enabled skipping back to the previous sentence while playing. If the current
sentence has played for less than a certain number of seconds, it's played
from the beginning. This threshold can be set in the configuration and is
3 seconds by default. Button and keyborad shortcut (ctrl + down arrow by
deafult) was added.

Bug: T133687
Change-Id: Ib63a013f525ce41f4e03bccf115ebe2a15fe4992
---
M Hooks.php
M extension.json
M modules/ext.wikispeech.css
M modules/ext.wikispeech.js
M tests/qunit/ext.wikispeech.test.js
5 files changed, 222 insertions(+), 61 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Wikispeech 
refs/changes/64/314664/1

diff --git a/Hooks.php b/Hooks.php
index a7c3211..0d15c02 100644
--- a/Hooks.php
+++ b/Hooks.php
@@ -120,6 +120,9 @@
                global $wgWikispeechKeyboardShortcuts;
                $vars['wgWikispeechKeyboardShortcuts'] =
                        $wgWikispeechKeyboardShortcuts;
+               global $wgWikispeechSkipBackRewindsThreshold;
+               $vars['wgWikispeechSkipBackRewindsThreshold'] =
+                       $wgWikispeechSkipBackRewindsThreshold;
                return true;
        }
 }
diff --git a/extension.json b/extension.json
index efba7c8..6f0c748 100644
--- a/extension.json
+++ b/extension.json
@@ -80,10 +80,15 @@
                                "key": 39,
                                "modifiers": [ "ctrl" ]
                        },
+                       "skipBackSentence": {
+                               "key": 37,
+                               "modifiers": [ "ctrl" ]
+                       },
                        "skipAheadWord": {
                                "key": 40,
                                "modifiers": [ "ctrl" ]
                        }
-               }
+               },
+               "WikispeechSkipBackRewindsThreshold": 3.0
        }
 }
diff --git a/modules/ext.wikispeech.css b/modules/ext.wikispeech.css
index 0ea613c..d362b04 100644
--- a/modules/ext.wikispeech.css
+++ b/modules/ext.wikispeech.css
@@ -14,6 +14,10 @@
        content: "Skip ahead sentence";
 }
 
+.ext-wikispeech-skip-back-sentence:after {
+       content: "Skip back sentence";
+}
+
 .ext-wikispeech-skip-ahead-word:after {
        content: "Skip ahead word";
 }
diff --git a/modules/ext.wikispeech.js b/modules/ext.wikispeech.js
index 1a3b0b4..7bce8a6 100644
--- a/modules/ext.wikispeech.js
+++ b/modules/ext.wikispeech.js
@@ -5,28 +5,39 @@
                self = this;
                $currentUtterance = $();
 
-               this.addButtons = function () {
-                       self.addPlayStopButton();
-                       self.addSkipAheadSentenceButton();
-                       self.addSkipAheadWordButton();
-               };
-
                /**
-                * Add a button for starting and stopping recitation to the 
page.
-                *
-                * When no utterance is playing, clicking starts the first 
utterance.
-                * When an utterance is being played, clicking stops the 
playback.
-                * The button changes appearance to reflect its current 
function.
+                * Add buttons for controlling playback to the top of the 
article.
                 */
 
-               this.addPlayStopButton = function () {
-                       var $playStopButton = $( '<button></button>' )
-                               .attr( 'id', 'ext-wikispeech-play-stop-button' )
-                               .addClass( 'ext-wikispeech-play' );
-                       $( '#firstHeading' ).append( $playStopButton );
-                       // For some reason, testing doesn't work with
-                       // .click( self.playOrStop ).
-                       $playStopButton.click( function () { self.playOrStop(); 
} );
+               this.addButtons = function () {
+                       self.addButton(
+                               'ext-wikispeech-play-stop-button',
+                               'ext-wikispeech-play',
+                               self.playOrStop
+                       );
+                       self.addButton(
+                               'ext-wikispeech-skip-ahead-sentence-button',
+                               'ext-wikispeech-skip-ahead-sentence',
+                               self.skipAheadUtterance
+                       );
+                       self.addButton(
+                               'ext-wikispeech-skip-back-sentence-button',
+                               'ext-wikispeech-skip-back-sentence',
+                               self.skipBackUtterance
+                       );
+                       self.addButton(
+                               'ext-wikispeech-skip-ahead-word-button',
+                               'ext-wikispeech-skip-ahead-word',
+                               self.skipAheadToken
+                       );
+               };
+
+               this.addButton = function ( id, cssClass, onClickFunction ) {
+                       var $button = $( '<button></button>' )
+                               .attr( 'id', id )
+                               .addClass( cssClass );
+                       $( '#firstHeading' ).append( $button );
+                       $button.click( onClickFunction );
                };
 
                /**
@@ -101,23 +112,6 @@
                };
 
                /**
-                * Add a button for skipping to the next sentence.
-                *
-                * This actually skips to the next utterance; it's assumed that 
the
-                * utterances are sentences, where titles count as sentences.
-                */
-
-               this.addSkipAheadSentenceButton = function () {
-                       var $skipAheadSentenceButton = $( '<button></button>' )
-                               .attr( 'id', 
'ext-wikispeech-skip-ahead-sentence-button' )
-                               .addClass( 'ext-wikispeech-skip-ahead-sentence' 
);
-                       $( '#firstHeading' ).append( $skipAheadSentenceButton );
-                       $skipAheadSentenceButton.click( function () {
-                               self.skipAheadUtterance();
-                       } );
-               };
-
-               /**
                 * Skip to the next utterance.
                 *
                 * Stop the current utterance and start playing the next one.
@@ -156,17 +150,30 @@
                };
 
                /**
-                * Add a button for skipping to the next word.
+                * Skip to the previous utterance.
+                *
+                * Stop the current utterance and start playing the previous 
one. If
+                * the first utterance is playing, restart it.
                 */
 
-               this.addSkipAheadWordButton = function () {
-                       var $button = $( '<button></button>' )
-                               .attr( 'id', 
'ext-wikispeech-skip-ahead-word-button' )
-                               .addClass( 'ext-wikispeech-skip-ahead-word' );
-                       $( '#firstHeading' ).append( $button );
-                       $button.click( function () {
-                               self.skipAheadToken();
-                       } );
+               this.skipBackUtterance = function () {
+                       var previousUtterance, rewindThreshold, $audio, time;
+
+                       previousUtterance =
+                               self.getPreviousUtterance( $currentUtterance );
+                       if ( previousUtterance.length ) {
+                               rewindThreshold = mw.config.get(
+                                       'wgWikispeechSkipBackRewindsThreshold' 
);
+                               $audio = $currentUtterance.children( 'audio' );
+                               time = $audio.prop( 'currentTime' );
+                               if ( time > rewindThreshold ) {
+                                       $audio.prop( 'currentTime', 0.0 );
+                               } else {
+                                       self.playUtterance( previousUtterance );
+                               }
+                       } else if ( self.isPlaying() ) {
+                               self.play();
+                       }
                };
 
                /**
@@ -240,6 +247,11 @@
                                ) {
                                        self.skipAheadUtterance();
                                        return false;
+                               } else if ( self.eventMatchShortcut(
+                                       event,
+                                       shortcuts.skipBackSentence )
+                               ) {
+                                       self.skipBackUtterance();
                                } else if (
                                        self.eventMatchShortcut( event, 
shortcuts.skipAheadWord )
                                ) {
@@ -310,6 +322,55 @@
                };
 
                /**
+                * Get the utterance after the given utterance.
+                *
+                * @param $utterance The original utterance.
+                * @return The utterance after the original utterance.
+                */
+
+               this.getNextUtterance = function ( $utterance ) {
+                       return self.getUtteranceByOffset( $utterance, 1 );
+               };
+
+               /**
+                * Get the utterance by offset from another utterance.
+                *
+                * @param $utterance The original utterance.
+                * @param {number} offest The difference, in index, to the 
wanted
+                *  utterance. Can be negative for preceeding 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.
+                */
+
+               this.getUtteranceByOffset = function ( $utterance, offset ) {
+                       var utteranceIdParts, nextUtteranceIndex, 
nextUtteranceId;
+
+                       if ( !$utterance.length ) {
+                               return $();
+                       }
+                       // Utterance id's follow the pattern "utterance-x", 
where x is
+                       // the index.
+                       utteranceIdParts = $utterance.attr( 'id' ).split( '-' );
+                       nextUtteranceIndex =
+                               parseInt( utteranceIdParts[ 1 ], 10 ) + offset;
+                       utteranceIdParts[ 1 ] = nextUtteranceIndex;
+                       nextUtteranceId = utteranceIdParts.join( '-' );
+                       return $( '#' + nextUtteranceId );
+               };
+
+               /**
+                * Get the utterance before the given utterance.
+                *
+                * @param $utterance The original utterance.
+                * @return The utterance before the original utterance.
+                */
+
+               this.getPreviousUtterance = function ( $utterance ) {
+                       return self.getUtteranceByOffset( $utterance, -1 );
+               };
+
+               /**
                 * Request audio for an utterance.
                 *
                 * Adds audio and token elements when the response is received.
diff --git a/tests/qunit/ext.wikispeech.test.js 
b/tests/qunit/ext.wikispeech.test.js
index cfbbd6e..b5bb6bf 100644
--- a/tests/qunit/ext.wikispeech.test.js
+++ b/tests/qunit/ext.wikispeech.test.js
@@ -33,11 +33,19 @@
                                                key: 39,
                                                modifiers: [ 'ctrl' ]
                                        },
+                                       skipBackSentence: {
+                                               key: 37,
+                                               modifiers: [ 'ctrl' ]
+                                       },
                                        skipAheadWord: {
                                                key: 40,
                                                modifiers: [ 'ctrl' ]
                                        }
                                }
+                       );
+                       mw.config.set(
+                               'wgWikispeechSkipBackRewindsThreshold',
+                               3.0
                        );
                },
                teardown: function () {
@@ -170,7 +178,7 @@
        } );
 
        QUnit.test( 'addButtons()', function ( assert ) {
-               assert.expect( 3 );
+               assert.expect( 4 );
                wikispeech.addButtons();
 
                assert.strictEqual(
@@ -179,6 +187,10 @@
                );
                assert.strictEqual(
                        $( '#firstHeading 
#ext-wikispeech-skip-ahead-sentence-button' ).length,
+                       1
+               );
+               assert.strictEqual(
+                       $( '#firstHeading 
#ext-wikispeech-skip-back-sentence-button' ).length,
                        1
                );
                assert.strictEqual(
@@ -204,13 +216,24 @@
         */
 
        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;
+               };
                wikispeech.addButtons();
-               sinon.spy( wikispeech, functionName );
 
                $( buttonId ).click();
 
-               assert.strictEqual( wikispeech[ functionName ].called, true );
+               assert.strictEqual( called, true );
+               // Restore the tested function.
+               wikispeech[ functionName ] = baseFunction;
        }
 
        QUnit.test( 'Clicking skip ahead sentence button', function ( assert ) {
@@ -218,6 +241,14 @@
                        assert,
                        'skipAheadUtterance',
                        '#ext-wikispeech-skip-ahead-sentence-button'
+               );
+       } );
+
+       QUnit.test( 'Clicking skip back sentence button', function ( assert ) {
+               testClickButton(
+                       assert,
+                       'skipBackUtterance',
+                       '#ext-wikispeech-skip-back-sentence-button'
                );
        } );
 
@@ -302,7 +333,7 @@
 
        QUnit.test( 'stop()', function ( assert ) {
                assert.expect( 4 );
-               wikispeech.addPlayStopButton();
+               wikispeech.addButtons();
                wikispeech.play();
                wikispeech.prepareUtterance( $( '#utterance-0' ) );
                $( '#utterance-0 audio' ).prop( 'currentTime', 1 );
@@ -329,7 +360,7 @@
        QUnit.test( 'play()', function ( assert ) {
                var $firstUtterance = $( '#utterance-0' );
                assert.expect( 3 );
-               wikispeech.addPlayStopButton();
+               wikispeech.addButtons();
                wikispeech.prepareUtterance( $firstUtterance );
 
                wikispeech.play();
@@ -353,6 +384,7 @@
        QUnit.test( 'skipAheadUtterance()', function ( assert ) {
                assert.expect( 2 );
                wikispeech.prepareUtterance( $( '#utterance-0' ) );
+               wikispeech.prepareUtterance( $( '#utterance-1' ) );
                wikispeech.play();
 
                wikispeech.skipAheadUtterance();
@@ -372,6 +404,62 @@
                wikispeech.skipAheadUtterance();
 
                assert.strictEqual( wikispeech.stop.called, true );
+       } );
+
+       QUnit.test( 'skipBackUtterance()', function ( assert ) {
+               assert.expect( 2 );
+               wikispeech.prepareUtterance( $( '#utterance-0' ) );
+               wikispeech.prepareUtterance( $( '#utterance-1' ) );
+               wikispeech.playUtterance( $( '#utterance-1' ) );
+
+               wikispeech.skipBackUtterance();
+
+               assert.strictEqual( $( '#utterance-1 audio' ).prop( 'paused' ), 
true );
+               assert.strictEqual(
+                       $( '#utterance-0 audio' ).prop( 'paused' ),
+                       false
+               );
+       } );
+
+       QUnit.test( 'skipBackUtterance(): restart if first utterance', function 
( assert ) {
+               assert.expect( 2 );
+               wikispeech.prepareUtterance( $( '#utterance-0' ) );
+               wikispeech.playUtterance( $( '#utterance-0' ) );
+               $( '#utterance-0 audio' ).prop( 'currentTime', 1.0 );
+
+               wikispeech.skipBackUtterance();
+
+               assert.strictEqual(
+                       $( '#utterance-0 audio' ).prop( 'paused' ),
+                       false
+               );
+               assert.strictEqual(
+                       $( '#utterance-0 audio' ).prop( 'currentTime' ),
+                       0.0
+               );
+       } );
+
+       QUnit.test( 'skipBackUtterance(): restart if played long enough', 
function ( assert ) {
+               assert.expect( 3 );
+               wikispeech.prepareUtterance( $( '#utterance-0' ) );
+               wikispeech.prepareUtterance( $( '#utterance-1' ) );
+               wikispeech.playUtterance( $( '#utterance-1' ) );
+               $( '#utterance-1 audio' ).prop( 'currentTime', 3.1 );
+
+               wikispeech.skipBackUtterance();
+
+               assert.strictEqual(
+                       $( '#utterance-1 audio' ).prop( 'paused' ),
+                       false
+               );
+               assert.strictEqual(
+                       $( '#utterance-1 audio' ).prop( 'currentTime' ),
+                       0.0
+               );
+               assert.strictEqual(
+                       $( '#utterance-0 audio' ).prop( 'paused' ),
+                       true
+               );
        } );
 
        QUnit.test( 'getNextUtterance()', function ( assert ) {
@@ -643,14 +731,14 @@
                $( '#utterance-0' ).append(
                        $( '<tokens></tokens>' )
                                .append(
-                    $( '<token></token>' )
+                                       $( '<token></token>' )
                                                .attr( 'time', 0.0 )
-                )
+                               )
                                .append(
-                    $( '<token></token>' )
+                                       $( '<token></token>' )
                                                .attr( 'time', 1.0 )
-                )
-        );
+                               )
+               );
                wikispeech.play();
 
                wikispeech.skipAheadToken();
@@ -667,14 +755,14 @@
                $( '#utterance-0' ).append(
                        $( '<tokens></tokens>' )
                                .append(
-                    $( '<token></token>' )
+                                       $( '<token></token>' )
                                                .attr( 'time', 0.0 )
-                )
+                               )
                                .append(
-                    $( '<token></token>' )
+                                       $( '<token></token>' )
                                                .attr( 'time', 1.0 )
-                )
-        );
+                               )
+               );
                wikispeech.play();
                $( '#utterance-0 audio' ).prop( 'currentTime', 1.1 );
                sinon.spy( wikispeech, 'skipAheadUtterance' );

-- 
To view, visit https://gerrit.wikimedia.org/r/314664
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ib63a013f525ce41f4e03bccf115ebe2a15fe4992
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

Reply via email to