1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

extract voice processing

This commit is contained in:
Luke Pulverenti 2015-09-13 20:29:04 -04:00
parent 007ba834a9
commit 18895615b0
3 changed files with 304 additions and 441 deletions

View file

@ -348,9 +348,6 @@
// Automatically handle clicks and form submissions through Ajax, when same-domain
ajaxEnabled: true,
// Automatically load and show pages based on location.hash
hashListeningEnabled: true,
// disable to prevent jquery from bothering with links
linkBindingEnabled: true,
@ -374,8 +371,6 @@
// where it is provided on the window object
phonegapNavigationEnabled: false,
pushStateEnabled: true,
// allows users to opt in to ignoring content by marking a parent element as
// data-ignored
ignoreContentEnabled: false,
@ -441,17 +436,6 @@
nsNormalize: function( prop ) {
return nsNormalizeDict[ prop ] ||
( nsNormalizeDict[ prop ] = $.camelCase( $.mobile.ns + prop ) );
},
// Find the closest javascript page element to gather settings data jsperf test
// http://jsperf.com/single-complex-selector-vs-many-complex-selectors/edit
// possibly naive, but it shows that the parsing overhead for *just* the page selector vs
// the page and dialog selector is negligable. This could probably be speed up by
// doing a similar parent node traversal to the one found in the inherited theme code above
closestPageData: function( $target ) {
return $target
.closest( ":jqmData(role='page'), :jqmData(role='dialog')" )
.data( "mobile-page" );
}
});
@ -855,16 +839,6 @@ $.mobile.browser.oldIE = (function() {
})();
$.extend( $.support, {
// Note, Chrome for iOS has an extremely quirky implementation of popstate.
// We've chosen to take the shortest path to a bug fix here for issue #5426
// See the following link for information about the regex chosen
// https://developers.google.com/chrome/mobile/docs/user-agent#chrome_for_ios_user-agent
pushState: "pushState" in history &&
"replaceState" in history &&
// When running inside a FF iframe, calling replaceState causes an error
!( window.navigator.userAgent.indexOf( "Firefox" ) >= 0 && window.top !== window ) &&
( window.navigator.userAgent.search(/CriOS/) === -1 ),
mediaquery: $.mobile.media( "only all" ),
cssPseudoElement: !!propExists( "content" ),
touchOverflow: !!propExists( "overflowScrolling" ),
@ -908,18 +882,6 @@ $.mobile.ajaxBlacklist =
originalEventName: undefined,
// If pushstate support is present and push state support is defined to
// be true on the mobile namespace.
isPushStateEnabled: function() {
return $.support.pushState &&
this.isHashChangeEnabled();
},
// !! assumes mobile namespace is present
isHashChangeEnabled: function() {
return $.mobile.hashListeningEnabled === true;
},
// TODO a lot of duplication between popstate and hashchange
popstate: function( event ) {
var newEvent = new $.Event( "navigate" ),
@ -973,13 +935,8 @@ $.mobile.ajaxBlacklist =
self.bound = true;
if ( self.isPushStateEnabled() ) {
self.originalEventName = "popstate";
$win.bind( "popstate.navigate", self.popstate );
} else if ( self.isHashChangeEnabled() ) {
self.originalEventName = "hashchange";
$win.bind( "hashchange.navigate", self.hashchange );
}
$win.bind("popstate.navigate", self.popstate);
}
};
})( jQuery );
@ -2360,8 +2317,7 @@ $.widget( "mobile.page", {
this.ignoreInitialHashChange = true;
$.mobile.window.bind({
"popstate.history": $.proxy( this.popstate, this ),
"hashchange.history": $.proxy( this.hashchange, this )
"popstate.history": $.proxy( this.popstate, this )
});
};
@ -2420,8 +2376,7 @@ $.widget( "mobile.page", {
// TODO reconsider name
go: function( url, data, noEvents ) {
var state, href, hash, popstateEvent,
isPopStateEvent = $.event.special.navigate.isPushStateEnabled();
var state, href, hash, popstateEvent;
// Get the url as it would look squashed on to the current resolution url
href = path.squash( url );
@ -2464,21 +2419,19 @@ $.widget( "mobile.page", {
title: document.title
}, data);
if ( isPopStateEvent ) {
popstateEvent = new $.Event( "popstate" );
popstateEvent = new $.Event("popstate");
popstateEvent.originalEvent = {
type: "popstate",
state: null
};
this.squash( url, state );
this.squash(url, state);
// Trigger a new faux popstate event to replace the one that we
// caught that was triggered by the hash setting above.
if ( !noEvents ) {
if (!noEvents) {
this.ignorePopState = true;
$.mobile.window.trigger( popstateEvent );
}
$.mobile.window.trigger(popstateEvent);
}
// record the history entry so that the information can be included
@ -2497,12 +2450,6 @@ $.widget( "mobile.page", {
popstate: function( event ) {
var hash, state;
// Partly to support our test suite which manually alters the support
// value to test hashchange. Partly to prevent all around weirdness
if ( !$.event.special.navigate.isPushStateEnabled() ) {
return;
}
// If this is the popstate triggered by the actual alteration of the hash
// prevent it completely. History is tracked manually
if ( this.preventHashAssignPopState ) {
@ -2572,62 +2519,6 @@ $.widget( "mobile.page", {
event.historyState.direction = direction;
}
});
},
// NOTE must bind before `navigate` special event hashchange binding otherwise the
// navigation data won't be attached to the hashchange event in time for those
// bindings to attach it to the `navigate` special event
// TODO add a check here that `hashchange.navigate` is bound already otherwise it's
// broken (exception?)
hashchange: function( event ) {
var history, hash;
// If hashchange listening is explicitly disabled or pushstate is supported
// avoid making use of the hashchange handler.
if (!$.event.special.navigate.isHashChangeEnabled() ||
$.event.special.navigate.isPushStateEnabled() ) {
return;
}
// On occasion explicitly want to prevent the next hash from propogating because we only
// with to alter the url to represent the new state do so here
if ( this.preventNextHashChange ) {
this.preventNextHashChange = false;
event.stopImmediatePropagation();
return;
}
history = this.history;
hash = path.parseLocation().hash;
// If this is a hashchange caused by the back or forward button
// make sure to set the state of our history stack properly
this.history.direct({
url: hash,
// When the url is either forward or backward in history include the entry
// as data on the event object for merging as data in the navigate event
present: function( historyEntry, direction ) {
// make sure to create a new object to pass down as the navigate event data
event.hashchangeState = $.extend({}, historyEntry);
event.hashchangeState.direction = direction;
},
// When we don't find a hash in our history clearly we're aiming to go there
// record the entry as new for future traversal
//
// NOTE it's not entirely clear that this is the right thing to do given that we
// can't know the users intention. It might be better to explicitly _not_
// support location.hash assignment in preference to $.navigate calls
// TODO first arg to add should be the href, but it causes issues in identifying
// embeded pages
missing: function() {
history.add( hash, {
hash: hash,
title: document.title
});
}
});
}
});
})( jQuery );
@ -2792,24 +2683,7 @@ $.widget( "mobile.page", {
go: function( steps ) {
//if hashlistening is enabled use native history method
if ( $.mobile.hashListeningEnabled ) {
window.history.go( steps );
} else {
//we are not listening to the hash so handle history internally
var activeIndex = $.mobile.navigate.history.activeIndex,
index = activeIndex + parseInt( steps, 10 ),
url = $.mobile.navigate.history.stack[ index ].url,
direction = ( steps >= 1 )? "forward" : "back";
//update the history object
$.mobile.navigate.history.activeIndex = index;
$.mobile.navigate.history.previousIndex = activeIndex;
//change to the new page
this.change( url, { direction: direction, changeHash: false, fromHashChange: true } );
}
window.history.go(steps);
},
// TODO rename _handleDestination
@ -3542,7 +3416,7 @@ $.widget( "mobile.page", {
role: settings.role
};
if ( settings.changeHash !== false && $.mobile.hashListeningEnabled ) {
if ( settings.changeHash !== false ) {
$.mobile.navigate( this.window[ 0 ].encodeURI( url ), params, true);
} else if ( toPage[ 0 ] !== $.mobile.firstPage[ 0 ] ) {
$.mobile.navigate.history.add( url, params );
@ -3874,34 +3748,25 @@ $.widget( "mobile.page", {
// the hash is not valid (contains more than one # or does not start with #)
// or there is no page with that hash, change to the first page in the DOM
// Remember, however, that the hash can also be a path!
if ( ! ( $.mobile.hashListeningEnabled &&
$.mobile.path.isHashValid( location.hash ) &&
if ( ! ( $.mobile.path.isHashValid( location.hash ) &&
( $( hashPage ).is( "[data-role='page']" ) ||
$.mobile.path.isPath( hash ) ||
hash === $.mobile.dialogHashKey ) ) ) {
// make sure to set initial popstate state if it exists
// so that navigation back to the initial page works properly
if ( $.event.special.navigate.isPushStateEnabled() ) {
$.mobile.navigate.navigator.squash( path.parseLocation().href );
}
$.mobile.navigate.navigator.squash(path.parseLocation().href);
$.mobile.changePage( $.mobile.firstPage, {
reverse: true,
changeHash: false,
fromHashChange: true
});
} else {
// trigger hashchange or navigate to squash and record the correct
// history entry for an initial hash path
if ( !$.event.special.navigate.isPushStateEnabled() ) {
$window.trigger( "hashchange", [true] );
} else {
// TODO figure out how to simplify this interaction with the initial history entry
// at the bottom js/navigate/navigate.js
$.mobile.navigate.history.stack = [];
$.mobile.navigate( $.mobile.path.isPath( location.hash ) ? location.hash : location.href );
}
$.mobile.navigate($.mobile.path.isPath(location.hash) ? location.hash : location.href);
}
}
});

View file

@ -0,0 +1,244 @@
define([], function () {
function parseTextInternal(text) {
var result = {
action: '',
itemName: '',
itemType: '',
category: '',
filters: [],
removeWords: [],
sortby: '',
sortorder: 'Ascending',
limit: null,
userId: Dashboard.getCurrentUserId()
};
var textLower = text.toLowerCase();
var words = text.toLowerCase().split(' ');
var displayWords = [
'show',
'pull up',
'display',
'go to',
'view'
];
if (displayWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) {
if (words.indexOf('guide') != -1) {
result.action = 'show';
result.category = 'tvguide';
}
if (words.indexOf('recordings') != -1) {
result.action = 'show';
result.category = 'recordings';
}
result.removeWords = displayWords;
return result;
}
var searchWords = [
'search',
'search for',
'find',
'query'
];
if (searchWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) {
// Search
result.action = 'search';
result.removeWords = searchWords;
return result;
}
var playWords = [
'play',
'watch'
];
if (playWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) {
// Play
result.action = 'play';
result.removeWords = playWords;
return result;
}
var controlWords = [
'use',
'control'
];
if (controlWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) {
// Play
result.action = 'control';
result.removeWords = controlWords;
return result;
}
var enableWords = [
'enable',
'turn on'
];
if (enableWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) {
// Play
result.action = 'enable';
result.removeWords = enableWords;
return result;
}
var disableWords = [
'disable',
'turn off'
];
if (disableWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) {
// Play
result.action = 'disable';
result.removeWords = disableWords;
return result;
}
var toggleWords = [
'toggle'
];
if (toggleWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) {
// Play
result.action = 'toggle';
result.removeWords = toggleWords;
return result;
}
if (words.indexOf('shuffle') != -1) {
// Play
result.action = 'shuffle';
result.removeWords.push('shuffle');
return result;
}
if (words.indexOf('record') != -1) {
// Record
result.action = 'record';
result.removeWords.push('record');
return result;
}
if (words.indexOf('guide') != -1) {
result.action = 'show';
result.category = 'tvguide';
return result;
}
return result;
}
function parseContext(text, result) {
text = text.toLowerCase();
var i, length;
for (i = 0, length = result.removeWords.length; i < length; i++) {
text = text.replace(result.removeWords[i], '');
}
text = text.trim();
var removeAtStart = [
'my'
];
for (i = 0, length = removeAtStart.length; i < length; i++) {
if (text.indexOf(removeAtStart[i]) == 0) {
text = text.substring(removeAtStart[i].length);
}
}
result.what = text;
text = text.trim();
var words = text.toLowerCase().split(' ');
if (words.indexOf('favorite') != -1) {
result.filters.push('favorite');
}
if (text.indexOf('latest movies') != -1 || text.indexOf('latest films') != -1) {
result.sortby = 'datecreated';
result.sortorder = 'Descending';
result.filters.push('unplayed');
result.itemType = 'Movie';
return;
}
if (text.indexOf('latest episodes') != -1) {
result.sortby = 'datecreated';
result.sortorder = 'Descending';
result.filters.push('unplayed');
result.itemType = 'Episode';
return;
}
if (text.indexOf('next up') != -1) {
result.category = 'nextup';
return;
}
if (text.indexOf('movies') != -1 || text.indexOf('films') != -1) {
result.itemType = 'Movie';
return;
}
if (text.indexOf('shows') != -1 || text.indexOf('series') != -1) {
result.itemType = 'Series';
return;
}
if (text.indexOf('songs') != -1) {
result.itemType = 'Audio';
return;
}
}
return function (text) {
var result = parseTextInternal(text);
parseContext(text, result);
return result;
}
});

View file

@ -51,283 +51,34 @@
var deferred = DeferredBuilder.Deferred();
processTextInternal(text, deferred);
return deferred.promise();
}
function parseContext(text, result) {
text = text.toLowerCase();
var i, length;
for (i = 0, length = result.removeWords.length; i < length; i++) {
text = text.replace(result.removeWords[i], '');
}
text = text.trim();
var removeAtStart = [
'my'
];
for (i = 0, length = removeAtStart.length; i < length; i++) {
if (text.indexOf(removeAtStart[i]) == 0) {
text = text.substring(removeAtStart[i].length);
}
}
result.what = text;
text = text.trim();
var words = text.toLowerCase().split(' ');
if (words.indexOf('favorite') != -1) {
result.filters.push('favorite');
}
if (text.indexOf('latest movies') != -1 || text.indexOf('latest films') != -1) {
result.sortby = 'datecreated';
result.sortorder = 'Descending';
result.filters.push('unplayed');
result.itemType = 'Movie';
return;
}
if (text.indexOf('latest episodes') != -1) {
result.sortby = 'datecreated';
result.sortorder = 'Descending';
result.filters.push('unplayed');
result.itemType = 'Episode';
return;
}
if (text.indexOf('next up') != -1) {
result.category = 'nextup';
return;
}
if (text.indexOf('movies') != -1 || text.indexOf('films') != -1) {
result.itemType = 'Movie';
return;
}
if (text.indexOf('shows') != -1 || text.indexOf('series') != -1) {
result.itemType = 'Series';
return;
}
if (text.indexOf('songs') != -1) {
result.itemType = 'Audio';
return;
}
}
function parseText(text) {
var result = {
action: '',
itemName: '',
itemType: '',
category: '',
filters: [],
removeWords: [],
sortby: '',
sortorder: 'Ascending',
limit: null,
userId: Dashboard.getCurrentUserId()
};
var textLower = text.toLowerCase();
var words = text.toLowerCase().split(' ');
var displayWords = [
'show',
'pull up',
'display',
'go to',
'view'
];
if (displayWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) {
if (words.indexOf('guide') != -1) {
result.action = 'show';
result.category = 'tvguide';
}
if (words.indexOf('recordings') != -1) {
result.action = 'show';
result.category = 'recordings';
}
result.removeWords = displayWords;
return result;
}
var searchWords = [
'search',
'search for',
'find',
'query'
];
if (searchWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) {
// Search
result.action = 'search';
result.removeWords = searchWords;
return result;
}
var playWords = [
'play',
'watch'
];
if (playWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) {
// Play
result.action = 'play';
result.removeWords = playWords;
return result;
}
var controlWords = [
'use',
'control'
];
if (controlWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) {
// Play
result.action = 'control';
result.removeWords = controlWords;
return result;
}
var enableWords = [
'enable',
'turn on'
];
if (enableWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) {
// Play
result.action = 'enable';
result.removeWords = enableWords;
return result;
}
var disableWords = [
'disable',
'turn off'
];
if (disableWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) {
// Play
result.action = 'disable';
result.removeWords = disableWords;
return result;
}
var toggleWords = [
'toggle'
];
if (toggleWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) {
// Play
result.action = 'toggle';
result.removeWords = toggleWords;
return result;
}
if (words.indexOf('shuffle') != -1) {
// Play
result.action = 'shuffle';
result.removeWords.push('shuffle');
return result;
}
if (words.indexOf('record') != -1) {
// Record
result.action = 'record';
result.removeWords.push('record');
return result;
}
if (words.indexOf('guide') != -1) {
result.action = 'show';
result.category = 'tvguide';
return result;
}
return result;
}
function processTextInternal(text, deferred) {
require(['voice/textprocessor-en-us.js'], function (parseText) {
var result = parseText(text);
switch (result.action) {
case 'show':
parseContext(text, result);
showCommand(result);
break;
case 'play':
parseContext(text, result);
playCommand(result);
break;
case 'shuffle':
parseContext(text, result);
playCommand(result, true);
break;
case 'search':
parseContext(text, result);
playCommand(result);
break;
case 'control':
parseContext(text, result);
controlCommand(result);
break;
case 'enable':
parseContext(text, result);
enableCommand(result);
break;
case 'disable':
parseContext(text, result);
disableCommand(result);
break;
case 'toggle':
parseContext(text, result);
toggleCommand(result);
break;
default:
@ -336,6 +87,9 @@
}
deferred.resolve();
});
return deferred.promise();
}
function showCommand(result) {