1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

Enter Webz beta release - web browser for the TiVo!

Discussion in 'Developers Corner' started by davidblackledge, Jan 6, 2013.

  1. Sep 1, 2013 #61 of 165
    davidblackledge

    davidblackledge Registered lÜser

    466
    0
    Sep 9, 2008
    NM
    DAMMIT!!
     
  2. Sep 4, 2013 #62 of 165
    Fofer

    Fofer XenForo Rocks! TCF Club

    82,090
    286
    Oct 29, 2000
    Your work here was amazing. In fact, there are probably users with older TiVos who would still be happy to see it, and use it, since that new built-in browser feature is a Roamio exclusive. Who knows, maybe that new feature will bring renewed attention to Enter Webz?!?!

    I personally have little interest in a Roamio at this time... but would love to enhance my current Premiere with cool add-ons such as yours.

    And I'm sure you learned a great deal developing Enter Webz. Kudos to you, and thanks. Keep your chin up! You're brilliant!
     
  3. Nov 1, 2013 #63 of 165
    davidblackledge

    davidblackledge Registered lÜser

    466
    0
    Sep 9, 2008
    NM
    ​​​October Enter Webz Beta update - the Halloween Update! :eek: (ok, there's nothing scary about this...unless you mean scary AWESOME!)

    Sorry about the 7 month lag. I was occupied with my wife's open-heart surgery & continuing recovery, and I have been playing a lot of Team Fortress 2 ;] (Pyro FTW!)

    ​Download it from david.blackledge.com/tivo/enterwebz​

    Major enhancements (More detailed list is in the documentation):


    • Videos and Audio play in-browser, PIP works so you can keep browsing, much better support in web pages.
    • RSS & Atom Feed support - often a better option than the actual page
    • More Images load now - Facebook pictures and Gocomics.com work! Even Icanhascheezburger.com works! (RSS feed is better)
    • ​thumztak Organizing - groups, order, editing
    • Cookie improvements - lots of pages work better​, and "delete all cookies" to fix problems
    • Fixed Mac and Linux issues (hopefully)
    Also: More fonts (serif, fixed, and script!), better navigation (mobile num shortcuts, "B"ack, Clear only quits), new UI graphics (still needs work), useful idle handling, fixed Live TV & many bugs, added new bugs ;]

    ​I want to emphasize the new RSS support. It is the better way to browse most content, providing a much simpler web page. Archive.org has one for each category's recent uploads, futoncritic's covers half the site, and tivocommunity.com and most blogs and news sites have one. Note that RSS via feedburner (such as ICanHasCheezburger) now ignores their crazy stylesheet.

    My Next Goals: custom in-page focus manager and highlights, youtube playlists and on-the-fly audio/video playlists, complete main theme and theme system, edit thumztak url and icon, bigger list of useful compatible links, bug fixes... and a Public Server! (and some ads to pay for it) ​

    Some known website issues:
    • Google services work fine... but later password verification can get stuck and you have to delete all cookies. The Google search page refuses to show the mobile version.
    • Yahoo services refuse to login with Enter Webz' Mobile mode, and logins aren't quite right in non-Mobile mode.​
    • Facebook videos don't show up - complain to Facebook. The same thing happens on my PalmOS phone's browser - they just don't send any HTML for the video at all!
    • While table layout was improved, the last column is still often broken, even a little worse now.
    • Vimeo: home page will reboot your TiVo. I did a lot of work to support their videos, but they recently changed so now I can't support it at all.

    Realistically, Enter Webz is better thought of as an App Platform for the TiVo. I've gathered some examples that use no scripting or plugins and are simple games, slideshows, and jukeboxes that work nicely. Targeting Enter Webz with a website design is much easier than writing an HME App.

    Please post your favorite websites/feeds/apps to view in Enter Webz so I can add them to my list! I like http://m.podcast.tv and http://shoutcast.com not to mention http://m.facebook.com http://xkcd.com and http://gocomics.com

    And post your favorite sites and RSS feeds that don't quite work so I can try to tackle bugs that might be in your way.
     
  4. Nov 1, 2013 #64 of 165
    dadrepus

    dadrepus Member

    113
    0
    Jan 4, 2012
    Columbia, MD
    Just opened the Command file in terminal but other than this:
    Last login: Wed Oct 30 22:10:05 on console
    Alexs-Mac-mini:~ Alex$ /Applications/EnterWebzDist-2013-10-31/Enter\ Webz.command ; exit;
    logout

    [Process completed]
    Nothing else happened. So I tried to directly open the jar file. (Now with Mavericks 10.9 one cannot easily open files by an unrecognized developer. One must hold down control button while clicking on an application or file) Well, the jar fails to open. Says go to console app to find error. Look at console but cannot find error so far. don't know exactly what the error would be called. I will continue to examine console for error. Glad your wife is doing better
     
  5. Nov 1, 2013 #65 of 165
    davidblackledge

    davidblackledge Registered lÜser

    466
    0
    Sep 9, 2008
    NM
    Ok, somebody send me a Mac and I'll test it ;]
    Strange that the behavior seems so different. All I believe I did since last release is fix newlines in the command file and mark it as executable. :confused:

    Let me know if you find any output, (I'd hope relevant stuff would either mention "java" or "com.blackledge.david" etc. - the java package names) but otherwise we'll need a local Mac expert to get it working and enlighten us :/

    Thanks for trying it!
     
  6. Nov 3, 2013 #66 of 165
    davidblackledge

    davidblackledge Registered lÜser

    466
    0
    Sep 9, 2008
    NM
    In the latest release of Enter Webz:

    Video/PIP is handled with three features:
    1) all video (including "livetv:") is currently handled by launching TiVo's VideoPlayer or Youtube Player app with detail parameters passed in. But it now does it via the "Application as Stream" feature. This is broken in the Java SDK, but easily worked around (see the wiki).
    (in the future I should put "livetv:" in a custom handler so you don't have to wait for the VideoPlayer app to launch)
    2) Scaling, like I mentioned previously, is handled with a setting the View's scale and position, but the position has to NOT be up against the upper and left edges of the screen or the scale is ignored by the video stream (and destabilizes I think)
    3) Most interesting: I finally realized what the Send Event command is REALLY for (not just for animation chaining per the SDK documentation). It actually sends the event to the targeted Stream... if that stream is an Application, then it's like you hit that remote button. For my purposes, when the player is full screen, I forward every event except for two that I want to override (Zoom/Aspect/Window/PIP and Left - both of which scale/position to PIP mode). When it's in PIP mode I only forward the Pause button.


    Images: I guess all https resource URLs are mishandled by the TiVo, so that caused lots of images to fail in webpages... instead I just replace them all with http URLs and hope for the best. Usually it works. (there is a bug in this release where my "figure out the size" code isn't handling the replaced URLs correctly - fixed in next release). There are still lots of sites that protect against remote loading, and I haven't gotten a solution for telling the TiVo to send referer/cookie information in its request.


    Fonts: I found some freely distributable fonts that fit the bill and put them on my main website, then just tell the TiVo to download the font from there when I need it, and re-use the font resource for the rest of the session.
     
  7. Nov 3, 2013 #67 of 165
    wmcbrine

    wmcbrine Ziphead

    10,364
    22
    Aug 2, 2003
    Interesting. Uh, where exactly in the wiki?
     
  8. Nov 3, 2013 #68 of 165
    davidblackledge

    davidblackledge Registered lÜser

    466
    0
    Sep 9, 2008
    NM
    ok, the wiki's not that well organized.

    SDKs > Java SDK Bugs / Gotchas
    http://hmedev.wikidot.com/java-sdk-bugs-gotchas
    The last entry (parseQuery)

    That's specifically a bug in the Java SDK code itself.

    Actually, I'll soon stick on the wiki the three classes I put together to encapsulate all I could of the V49 changes for the Java SDK and to make using the 1.4.1e version of the SDK easier.

    Let's see if I can get them into this post:
    V49IHmeProtocol.java - just the additional constants for the keyboard and video status, etc.
    V49BApplicationFactory.java - report your app as V49 so you can use new features
    V49BApplication.java - helpers for several V49 features, and fixes for some problems (some are pre 1.4.1e problems that don't need fixing if you use the newer SDK, so I made them forward compatible fixes).

    Code:
    V49IHmeProtocol.java
    Code:
    package com.blackledge.david.tivo;
    
    import com.tivo.hme.interfaces.IHmeConstants;
    import com.tivo.hme.sdk.IHmeProtocol;
    
    /**
     * IHmeProtocol extension that supports some features beyond SDK 1.4.1
     * especially HTTP Header strings and Slide Remote key codes. Typically just use
     * {@link #KEY_OPT_ASCII_START}, {@link #KEY_OPT_ASCII_END}, and
     * {@link #KEY_OPT_ASCII_OFFSET} to get all the characters.
     * 
     */
    public interface V49IHmeProtocol extends IHmeProtocol {
    	/**
    	 * 0.49 - Slide Remote key events supported (no "supporting" sdk version)
    	 * (0.41 ({@link IHmeProtocol#VERSION_0_41}) supported by sdk 1.4, 0.43-0.45
    	 * ({@link IHmeProtocol#VERSION_0_43}-{@link IHmeProtocol#VERSION_0_45}) by
    	 * sdk 1.4.1e)
    	 */
    	public static final int VERSION_0_49 = (0 << 8) | 49;
    
    	/**
    	 * according to an error logged:
    	 * "rendered text exceeded max allowed dimensions of 1920x1080"
    	 */
    	public static final int LIMIT_TEXT_RENDER_WIDTH = 1920;
    	/**
    	 * according to an error logged:
    	 * "rendered text exceeded max allowed dimensions of 1920x1080"
    	 */
    	public static final int LIMIT_TEXT_RENDER_HEIGHT = 1080;
    	
    	/** Media status code, resource at end of stream. */
    	public static final int RSRC_STATUS_END = 11;
    	/** Media status code, resource at end of available buffer. */
    	public static final int RSRC_STATUS_BUFFER_OVERFLOW = 12;
    	
        /**
    	 * Zoom key, optional, relabel of {@link #KEY_OPT_ASPECT} key, which is same
    	 * code as {@link #KEY_OPT_WINDOW} and {@link #KEY_OPT_PIP} (22).
    	 */
        public static final int KEY_OPT_ZOOM			= KEY_OPT_WINDOW;
        
        // undefined keys in SDK: 0-21, 23, 25, 28-50
        
        /**
    	 * On Demand key, reserved for internal use. E.g. long blue ON DEMAND button
    	 * on Charter remote. This is sent in a key press event just before
    	 * attempting to "teleport" to the service. In anything below Series 4, no
    	 * teleport and not transmitted over HME, apparently.
    	 */
        public static final int KEY_OPT_ON_DEMAND = 30;
        
        // last SDK key code defined: KEY_OPT_DVD = 55;
        
    	/**
    	 * "A" yellow remote button, may only transmit on if identified as V49.
    	 */
    	public static final int KEY_OPT_A_YELLOW = 56;
    	/**
    	 * "B" blue remote button, may only transmit on if identified as V49.
    	 */
    	public static final int KEY_OPT_B_BLUE = 57;
    	/**
    	 * "C" red remote button, may only transmit on if identified as V49.
    	 */
    	public static final int KEY_OPT_C_RED = 58;
    	/**
    	 * "D" green remote button, may only transmit on if identified as V49.
    	 */
    	public static final int KEY_OPT_D_GREEN = 59;
    	
    	/**
    	 * Backspace (picture of a left arrow on slide remote) button code.<BR>
    	 */
    	public static final int KEY_OPT_BACKSPACE = 65;
    	
    	// codes 60-64 and 66-69 are unknown, currently
    	
    	/**
    	 * code returned by the RETURN key on the keyboard if it is
    	 * modified by Alt (or Windows or Ctrl (or possibly "sym"(bol))). Normally
    	 * that key sends KEY_SELECT.
    	 */
    	public static final int KEY_OPT_ALT_SELECT = 70;
    	/**
    	 * Left Shift button code. You do not need to process this to handle
    	 * capitalization, use KEY_OPT_ASCII_* to get the properly capitalized
    	 * character.
    	 */
    	public static final int KEY_OPT_LEFT_SHIFT = 71;
    	/**
    	 * Right Shift button code. You do not need to process this to handle
    	 * capitalization, use KEY_OPT_ASCII_* to get the properly capitalized
    	 * character.
    	 */
    	public static final int KEY_OPT_RIGHT_SHIFT = 72;
    	/**
    	 * Left Alt (and Windows) key button code for keyboards - POSSIBLY the
    	 * "sym"(bol) key for the Slide Remote (although there is only one, on the
    	 * right side). You do not need to process this to handle symbols, use
    	 * KEY_OPT_ASCII_* to get the properly capitalized character.
    	 */
    	public static final int KEY_OPT_LEFT_ALT = 73;
    	/**
    	 * Right Alt (and Windows) key button code for keyboards - POSSIBLY the
    	 * "sym"(bol) key for the Slide Remote. You do not need to process this to
    	 * handle symbols, use KEY_OPT_ASCII_* to get the properly capitalized
    	 * character.
    	 */
    	public static final int KEY_OPT_RIGHT_ALT = 74;
    	/**
    	 * Capslock button code. You do not need to process this to handle
    	 * capitalization, use KEY_OPT_ASCII_* to get the properly capitalized
    	 * character.
    	 */
    	public static final int KEY_OPT_CAPSLOCK = 75;
    	
    	/**
    	 * For all characters between KEY_OPT_ASCII_START and KEY_OPT_ASCII_END
    	 * (inclusive) subtract this value from the key code to get the ASCII
    	 * character they represent.
    	 */
    	public static final int KEY_OPT_ASCII_OFFSET = 65536; // 2^16
    	public static final int KEY_OPT_ASCII_SPACE = KEY_OPT_ASCII_OFFSET+' ';
    	// Sym Q
    	public static final int KEY_OPT_ASCII_EXCLAMATION = KEY_OPT_ASCII_OFFSET+'!';
    	// Sym L
    	public static final int KEY_OPT_ASCII_QUOTE = KEY_OPT_ASCII_OFFSET+'\"';
    	// Sym E
    	public static final int KEY_OPT_ASCII_HASH = KEY_OPT_ASCII_OFFSET+'#';
    	// Sym R
    	public static final int KEY_OPT_ASCII_DOLLAR = KEY_OPT_ASCII_OFFSET+'$';
    	// Sym T
    	public static final int KEY_OPT_ASCII_PERCENT = KEY_OPT_ASCII_OFFSET+'%';
    	// Sym U
    	public static final int KEY_OPT_ASCII_AMPERSAND = KEY_OPT_ASCII_OFFSET+'&';
    	// Sym K
    	public static final int KEY_OPT_ASCII_APOSTROPHE = KEY_OPT_ASCII_OFFSET+'\'';
    	// Sym O
    	public static final int KEY_OPT_ASCII_LEFTPAREN = KEY_OPT_ASCII_OFFSET+'(';
    	// Sym P
    	public static final int KEY_OPT_ASCII_RIGHTPAREN = KEY_OPT_ASCII_OFFSET+')';
    	// Sym I
    	public static final int KEY_OPT_ASCII_ASTERISK = KEY_OPT_ASCII_OFFSET+'*';
    	// Sym 1
    	public static final int KEY_OPT_ASCII_PLUS = KEY_OPT_ASCII_OFFSET+'+';
    	// Sym B
    	public static final int KEY_OPT_ASCII_COMMA = KEY_OPT_ASCII_OFFSET+',';
    	// Sym 2
    	public static final int KEY_OPT_ASCII_MINUS = KEY_OPT_ASCII_OFFSET+'-';
    	// Sym N
    	public static final int KEY_OPT_ASCII_PERIOD = KEY_OPT_ASCII_OFFSET+'.';
    	// Sym 5
    	public static final int KEY_OPT_ASCII_SLASH = KEY_OPT_ASCII_OFFSET+'/';
    	// 0-9 use KEY_NUM0-KEY_NUM9
    	// Sym J
    	public static final int KEY_OPT_ASCII_COLON = KEY_OPT_ASCII_OFFSET+':';
    	// Sym H
    	public static final int KEY_OPT_ASCII_SEMICOLON = KEY_OPT_ASCII_OFFSET+';';
    	// Sym Z
    	public static final int KEY_OPT_ASCII_LESS = KEY_OPT_ASCII_OFFSET+'<';
    	// Sym 3
    	public static final int KEY_OPT_ASCII_EQUAL = KEY_OPT_ASCII_OFFSET+'=';
    	// Sym X
    	public static final int KEY_OPT_ASCII_GREATER = KEY_OPT_ASCII_OFFSET+'>';
    	// Sym M
    	public static final int KEY_OPT_ASCII_QUESTION = KEY_OPT_ASCII_OFFSET+'?';
    	// Sym W
    	public static final int KEY_OPT_ASCII_AT = KEY_OPT_ASCII_OFFSET+'@';
    	public static final int KEY_OPT_ASCII_A_UPPER = KEY_OPT_ASCII_OFFSET+'A';
    	public static final int KEY_OPT_ASCII_Z_UPPER = KEY_OPT_ASCII_OFFSET+'Z';
    	// Sym F
    	public static final int KEY_OPT_ASCII_LEFTBRACKET = KEY_OPT_ASCII_OFFSET+'[';
    	// Sym V
    	public static final int KEY_OPT_ASCII_BACKSLASH = KEY_OPT_ASCII_OFFSET+'\\';
    	// Sym G
    	public static final int KEY_OPT_ASCII_RIGHTBRACKET = KEY_OPT_ASCII_OFFSET+']';
    	// Sym Y
    	public static final int KEY_OPT_ASCII_CIRCUMFLEX = KEY_OPT_ASCII_OFFSET+'^';
    	// Sym 7
    	public static final int KEY_OPT_ASCII_UNDERSCORE = KEY_OPT_ASCII_OFFSET+'_';
    	// Sym 9
    	public static final int KEY_OPT_ASCII_GRAVE = KEY_OPT_ASCII_OFFSET+'`';
    	public static final int KEY_OPT_ASCII_A_LOWER = KEY_OPT_ASCII_OFFSET+'a';
    	public static final int KEY_OPT_ASCII_Z_LOWER = KEY_OPT_ASCII_OFFSET+'z';
    	// Sym S
    	public static final int KEY_OPT_ASCII_LEFTBRACE = KEY_OPT_ASCII_OFFSET+'{';
    	// Sym C
    	public static final int KEY_OPT_ASCII_BAR = KEY_OPT_ASCII_OFFSET+'|';
    	// Sym D
    	public static final int KEY_OPT_ASCII_RIGHTBRACE = KEY_OPT_ASCII_OFFSET+'}';
    	// Sym A
    	public static final int KEY_OPT_ASCII_TILDE = KEY_OPT_ASCII_OFFSET+'~';
    	/**
    	 * space character - beginning of range of values that are ASCII characters
    	 * plus KEY_OPT_ASCII_OFFSET. Note that 0-9 do not show up this way,
    	 * instead sending the KEY_NUM* codes.
    	 */
    	public static final int KEY_OPT_ASCII_START = KEY_OPT_ASCII_SPACE;
    	/**
    	 * tilde character - end of range of values that are ASCII characters plus
    	 * KEY_OPT_ASCII_OFFSET. Note that 0-9 do not show up this way,
    	 * instead sending the KEY_NUM* codes.
    	 */
    	public static final int KEY_OPT_ASCII_END = KEY_OPT_ASCII_TILDE;
    
    	// as discovered by davidblackledge - Premiere only.
    	public static final int ID_FONT3_TTF = 13;
    	public static final int ID_FONT3ITALIC_TTF = 14;
    	public static final int ID_FONT3BOLD_TTF = 15;
    	// as discovered by wmcbrine - Premiere only
    	public static final int ID_SPEEDUP4_SOUND = 37;
    	
    	/**
    	 * HTTP Header sent in resource requests by the TiVo with the tsn (context
    	 * connection attribute "tsn") as value.
    	 */
    	public static final String HTTP_HEADER_TIVO_ID = "TiVo_TCD_ID";
    
    	/**
    	 * HTTP Header sent in resource requests by the TiVo with the TiVo Software
    	 * Version (Device Info event's "version") as value.
    	 */
    	public static final String HTTP_HEADER_TIVO_VERSION = "TiVo_SW_VER";
    
    	public static final String HTTP_HEADER_TIVO_DURATION = IHmeConstants.TIVO_DURATION;
    
    }
    
    V49BApplication.java
    Code:
    package com.blackledge.david.tivo;
    
    import java.awt.Rectangle;
    import java.io.UnsupportedEncodingException;
    import java.lang.ref.WeakReference;
    import java.net.URLDecoder;
    import java.util.Map;
    import java.util.LinkedHashMap;
    
    import com.tivo.hme.bananas.BApplication;
    import com.tivo.hme.interfaces.IContext;
    import com.tivo.hme.sdk.Application;
    import com.tivo.hme.sdk.HmeEvent;
    import com.tivo.hme.sdk.HmeEvent.DeviceInfo;
    import com.tivo.hme.sdk.IHmeProtocol;
    import com.tivo.hme.sdk.Resource;
    import com.tivo.hme.sdk.Version;
    
    /**
     * BApplication that fixes SDK bugs, supports some features beyond SDK 1.4.1 and
     * makes some things easier. See {@link V49BApplicationFactory} and
     * {@link V49IHmeProtocol} for other features.
     * <UL>
     * <LI>Use {@link V49BApplicationFactory} as superclass for your factory to tell the TiVo you're using V49 (or another version).
     * <LI>Constants for SDK resource strings *.ttf, *.snd
     * <LI>Constants for discovered Stream resources "livetv:" "loopset:" and
     * "recording:"
     * <LI>Getters for spacing when a "loopset:" is the background
     * <LI>Member variables for protocol version, tivo version, and tivo ID.
     * <LI>Resolution help: automatically set preferred resolution, template methods
     * for init and showing the first BScreen during resolution setting
     * <LI>Template method for cleaning up during {@link #destroy()} to prevent an
     * SDK 1.4 bug (not an issue in 1.4.1e)
     * <LI>Post Resource errors to the resource and with new version of
     * {@link #handleApplicationError(int, String, Integer)}
     * <LI>Fix SDK bug that broke Application-as-stream feature.
     * <LI>Support more TTF built-in fonts with new constant names for series-4-only
     * fonts, and with id numbers-as-font-names for arbitrary attempts.
     * <LI>SDK capability/version methods.
     * <LI>Backward compatibility of new SDK methods to old SDK. You should be able
     * to run this app in either SDK.
     * </UL>
     * 
     * @author David.Blackledge.com
     * 
     */
    public abstract class V49BApplication extends BApplication implements V49IHmeProtocol {
    
        public static final String TTF_SERIES4FONT3BOLD = "series4font3bold.ttf";
    	public static final String TTF_SERIES4FONTITALIC = "series4fontitalic.ttf";
    	public static final String TTF_SERIES4FONT = "series4font.ttf";
    	public static final String TTF_DEFAULT = "default.ttf"; //$NON-NLS-1$
        public static final String TTF_SYSTEM = "system.ttf"; //$NON-NLS-1$
        
        public static final String SOUND_BONK = "bonk.snd"; //$NON-NLS-1$
        public static final String SOUND_UPDOWN = "updown.snd"; //$NON-NLS-1$
        public static final String SOUND_THUMBSUP = "thumbsup.snd"; //$NON-NLS-1$
        public static final String SOUND_THUMBSDOWN = "thumbsdown.snd"; //$NON-NLS-1$
        public static final String SOUND_SELECT = "select.snd"; //$NON-NLS-1$
        public static final String SOUND_TIVO = "tivo.snd"; //$NON-NLS-1$
        public static final String SOUND_LEFT = "left.snd"; //$NON-NLS-1$
        public static final String SOUND_RIGHT = "right.snd"; //$NON-NLS-1$
        public static final String SOUND_PAGEUP = "pageup.snd"; //$NON-NLS-1$
        public static final String SOUND_PAGEDOWN = "pagedown.snd"; //$NON-NLS-1$
        public static final String SOUND_ALERT = "alert.snd"; //$NON-NLS-1$
        public static final String SOUND_DESELECT = "deselect.snd"; //$NON-NLS-1$
        public static final String SOUND_ERROR = "error.snd"; //$NON-NLS-1$
        public static final String SOUND_SLOWDOWN1 = "slowdown1.snd"; //$NON-NLS-1$
        public static final String SOUND_SPEEDUP1 = "speedup1.snd"; //$NON-NLS-1$
        public static final String SOUND_SPEEDUP2 = "speedup2.snd"; //$NON-NLS-1$
        public static final String SOUND_SPEEDUP3 = "speedup3.snd"; //$NON-NLS-1$
        
    	public static final String STREAM_LIVETV = "livetv:"; //$NON-NLS-1$
    	public static final String STREAM_LOOP_RED_CENTRAL = "loopset:Central"; //$NON-NLS-1$
    	public static final String STREAM_LOOP_PURPLE_SHOWCASES = "loopset:Showcases"; //$NON-NLS-1$
    	public static final String STREAM_LOOP_BLUE_SETUP = "loopset:Setup"; //$NON-NLS-1$
    	public static final String STREAM_LOOP_GREEN_NOWPLAYING = "loopset:NowPlaying"; //$NON-NLS-1$
    	public static final String STREAM_RECORDING_PREFIX = "recording:"; //$NON-NLS-1$
    	
        
    	/**
    	 * Protocol in use between the TiVo and the server. Used in methods like
    	 * {@link #isSlideRemoteSupported()}. Accurate/dynamic version of
    	 * {@link IHmeProtocol#VERSION}. Saved during
    	 * {@link #setContext(IContext, int)} call. Use V49BapplicationFactory as
    	 * your factory's supertype to register V49 as your preferred protocol
    	 * version.
    	 */
    	public int protocolVersion;
    	/**
    	 * Device Info event's "version" - the TiVo's Software Version. E.g.
    	 * 11.0k-01-2-652 . Available after DEVICE_INFO_EVENT comes in.
    	 */
    	protected String tivoVersion;
    	/**
    	 * The TiVo Service Number (tsn) uniquely identifying the connected TiVo DVR
    	 * device. Available after {@link #setContext(IContext, int)} is called.
    	 */
    	protected String tivoID;
    
    	/**
    	 * Does nothing.  Override this to output debug messages
    	 * @param message
    	 */
    	public void debug(String message) {
    	}
    
    	/**
    	 * Template method to do {@link #init(IContext)} tasks between super's init
    	 * and preferred resolution setting. Better to override this than init
    	 * unless you want to handle setting resolution yourself.
    	 */
    	protected void initBeforeResolutionSet(IContext context) {
    	}
    
    	/**
    	 * Template method to do {@link #rootBoundsChanged(Rectangle)} tasks between
    	 * super's rootBoundsChanged and a possible call to {@link #showFirstPage()}
    	 * if this is the first root bounds change during application
    	 * initialization. Override this, or it might be OK to override
    	 * rootBoundsChanged and just call its super method and ignore this.
    	 * 
    	 * @param rootBounds
    	 */
    	protected void rootBoundsChangedBeforeFirstPage(Rectangle rootBounds) {
    	}
    
    	/**
    	 * Override this to push the first BScreen of the application after
    	 * resolution issues are resolved.
    	 */
    	protected abstract void showFirstPage();
    
    	/**
    	 * Override this instead of {@link #destroy()} for SDK 1.4 to do safe cleanup during the
    	 * destroy operation. Prevents the loop that occurs if a fatal error happens
    	 * during destroy.  Not an issue in 1.4.1e
    	 */
    	protected void destroyCleanup() {
    	}
    	
    	
    	/* BEGIN fix JDK */
    	
    
    	// TODO future: handle unsupported chunks (e.g. Event 10)
    	// public boolean handleChunk(InputStream in)
    	// {
    	// synchronized (lock) {
    	// //debug("handleChunk()");
    	// boolean doMore = true;
    	//
    	// // flush any data that was generated from previous handling of
    	// // event
    	// flush();
    	//
    	// ChunkedInputStream chunkInStr = null;
    	// if (in instanceof ChunkedInputStream)
    	// {
    	// chunkInStr = (ChunkedInputStream)in;
    	// }
    	// else
    	// {
    	// chunkInStr = new ChunkedInputStream(in); //,
    	// IHmeConstants.TCP_BUFFER_SIZE);
    	// }
    	//
    	// // if ( protocolVersion < VERSION_0_40 )
    	// // {
    	// // chunkInStr.setUseVString( false );
    	// // }
    	// // else {
    	// chunkInStr.setUseVString( true );
    	// // }
    	//
    	// int opcode = -1;
    	// try {
    	// opcode = (int)chunkInStr.readVInt();
    	// } catch (IOException e) {
    	// // receiver closed - ignore
    	// // if (debug()) {
    	// // log(ILogger.LOG_DEBUG,
    	// // "Connection terminated by receiver");
    	// // }
    	// }
    	// if (opcode == -1) {
    	// doMore = false;
    	// return doMore;
    	// }
    	//
    	// HmeEvent evt = null;
    	// String eventString = null;
    	// try
    	// {
    	// switch (opcode) {
    	// case EVT_DEVICE_INFO:
    	// evt = new HmeEvent.DeviceInfo(chunkInStr);
    	// break;
    	// case EVT_APP_INFO:
    	// evt = new HmeEvent.ApplicationInfo(chunkInStr);
    	// break;
    	// case EVT_RSRC_INFO:
    	// evt = new HmeEvent.ResourceInfo(chunkInStr, this);
    	// break;
    	// case EVT_RESOLUTION_INFO:
    	// evt = new HmeEvent.ResolutionInfo(chunkInStr);
    	// break;
    	// case EVT_KEY:
    	// evt = new HmeEvent.Key(chunkInStr);
    	// break;
    	// case EVT_IDLE:
    	// evt = new HmeEvent.Idle(chunkInStr);
    	// break;
    	// case EVT_FONT_INFO:
    	// evt = new HmeEvent.FontInfo(chunkInStr);
    	// break;
    	// case EVT_INIT_INFO:
    	// evt = new HmeEvent.InitInfo(chunkInStr);
    	// break;
    	// default:
    	// eventString =
    	// "HmeEvent[opcode=" + opcode
    	// + ", id = " + chunkInStr.readVInt()
    	// + "]";
    	// // short clen = chunkInStr.readShort();
    	// // while (clen != 0) {
    	// // System.out.println(clen);
    	// // chunkInStr.skip(clen);
    	// // clen = chunkInStr.readShort();
    	// // }
    	//
    	// // System.out.println(eventString);
    	// // try {
    	// // System.out.println(chunkInStr.readInt());
    	// // System.out.println(chunkInStr.readDict());
    	// // } catch(IOException e1) {
    	// // chunkInStr.readTerminator();
    	// // throw e1;
    	// // }
    	// break;
    	// }
    	// if (evt != null) {
    	// evt.setMonitor(lock);
    	// }
    	// chunkInStr.readTerminator();
    	// }
    	// catch (IOException e) {
    	// evt = null;
    	// getLogger().log(ILogger.LOG_INFO, e);
    	// }
    	//
    	// //
    	// // Save the event, in case we need to dump the list
    	// //
    	// // if (eventsReceived != null) {
    	// // if (eventsReceived.size() >= MAX_RECEIVED_EVENTS_SIZE) {
    	// // // Startup is complete -- stop checking for protocol errs
    	// // startupComplete = true;
    	// // eventsReceived.clear();
    	// // eventsReceived = null;
    	// // } else {
    	// // if (evt != null) {
    	// // eventString = evt.toString();
    	// // }
    	// // if (eventString == null) {
    	// // eventString = "Bad HmeEvent[opcode="+opcode+", no ID]";
    	// // }
    	// // eventsReceived.add(eventString);
    	// // }
    	// // }
    	//
    	// if (evt == null) {
    	// getLogger().log(ILogger.LOG_DEBUG,
    	// "unknown event opcode : " + opcode);
    	// }
    	// else
    	// {
    	// // if any debugging is turned on, verify the event is kosher
    	// // if (debug()) {
    	// // evt.verify(logger);
    	// // }
    	//
    	// // debugEvents means to print each and every event received
    	// // if (debugEvents()) {
    	// getLogger().log(ILogger.LOG_DEBUG, "event " + evt);
    	// // }
    	//
    	// // if (!preprocessEvent(evt)) {
    	// //
    	// // // preprocessEvent() says this event should be discarded
    	// // if (eventsReceived != null) {
    	// // int i = eventsReceived.size() - 1;
    	// // eventsReceived.set(i,
    	// // eventsReceived.get(i).toString() + " [IGNORED]");
    	// // }
    	//
    	// // } else {
    	//
    	// // The app should receive this event.
    	// //
    	// // Only dispatch events after app.init() is called.
    	// // Save events before init(), to be dispatched later.
    	// //
    	// // if (appInitialized) {
    	// // dispatchEvent(evt);
    	// // } else if (queuedEvents != null) {
    	// // // Save the events to send after initialization
    	// // queuedEvents.add(evt);
    	// // }
    	// // }
    	// }
    	//
    	// // flush any data that was generated from handling of event
    	// flush();
    	//
    	// return doMore;
    	// } // sync
    	//
    	// } // handleChunk()
    
    
    	/**
    	 * unfortunately sdk's Resource.ResourceStatus isn't public, so we recreate
    	 * it here - all it does is use a different Event ID
    	 */
    	protected static class MockResourceStatus extends HmeEvent.ResourceInfo {
    		MockResourceStatus(int id, Resource rsrc, int status, Map map) {
    			super(EVT_RSRC_STATUS, id, rsrc, status, map);
    		}
    
    		public String toString() {
    			return getID() + ".RSRC_STATUS(" + statusToString(getStatus()) //$NON-NLS-1$
    					+ ")"; //$NON-NLS-1$
    		}
    	}
    
    	private boolean destroyedAlready = false;
    
    	public void destroy() {
    		// protect against SDK 1.4 bug: if there is a fatal error during below
    		// operations (e.g. remove operations) close gets called which calls
    		// destroy again.
    	
    		// what happens: after the 2nd call to here, we'll get HMEException from
    		// the remove call that caused it which contains the actual cause
    		// (unless it was originally an HMEException)
    		// setId(-1) has not been called on the object yet
    	
    		// SO: want to catch HMEExceptions if we want to finish our destroy
    		// operation
    		if (destroyedAlready)
    			return;
    		destroyedAlready = true;
    	
    		// template method
    		destroyCleanup();
    		// ... operations that could cause a fatal error, e.g. "remove" calls
    	
    		super.destroy(); // in case e.g. BApplication ever starts doing
    							// something there.
    	}
    	
    	/**
    	 * Improved version of
    	 * {@link Application#handleApplicationError(int, String)} that includes the
    	 * resourceId related to the error - critical for knowing when you have a
    	 * problem with a resource you created. Default implementation simply calls
    	 * {@link #handleApplicationError(int, String)}. ApplicationError codes 1
    	 * (e.g. "can't create rsrc. unsupported stream type
    	 * /avatar/cf1e61a4330e75d5d1d7a744c5ef38c4") and 3 (e.g. "resource 2091 not
    	 * found (type type[-1])") about a resource never provide a resourceinfo
    	 * event!
    	 * 
    	 * @param errorCode
    	 *            the error code of the error (one of the {@link IHmeProtocol}
    	 *            APP_ERROR_ constants)
    	 * @param errorText
    	 *            details of the error
    	 * @param resourceId
    	 *            the id associated with the error - could be null. Suitable for
    	 *            use with {@link #getResource(Object)} (or ((WeakReference)
    	 *            {@link #getResources()}.get(Object)).get() to avoid automatic
    	 *            SDK warning message when it's not found)
    	 * @return true if the event was handled.
    	 */
    	public boolean handleApplicationError(int errorCode, String errorText, Integer resourceId) {
    		if (handleApplicationError(errorCode, errorText)) {
    			return true;
    		} else {
    			// FIXME add processing needed by e.g. image exceeding max width or
    			// height (errorCode 2) with no resource id
    			return false;
    		}
    	}
    
    	/**
    	 * Perform some common handling - be sure to call this from subclasses that
    	 * override it. Sets {@link #tivoVersion} from DEVICE_INFO. Improves error
    	 * handling by posting resource errors to the resource itself, and calling
    	 * {@link #handleApplicationError(int, String, Integer)}, passing in
    	 * resource ID.
    	 * 
    	 */
    	public boolean handleEvent(HmeEvent event) {
    		// get tivo version from device info
    		if (event.getOpCode() == EVT_DEVICE_INFO) {
    			HmeEvent.DeviceInfo info = (DeviceInfo) event;
    			this.tivoVersion = (String) info.getMap().get("version"); //$NON-NLS-1$
    			// com.tivo.examine.idType.stationId=tivo,
    			// platform=Gen06,
    			// host=Bedroom,
    			// brand=TiVo,
    			// com.tivo.examine.showing=true,
    			// com.tivo.examine.idType.contentId=tivo,
    			// version=11.0k-01-2-652
    		}
    	
    		// ...
    		if (event.getOpCode() == EVT_APP_INFO) {
    			HmeEvent.ApplicationInfo info = (HmeEvent.ApplicationInfo) event;
    			if (info.getMap().get("error.code") != null) { //$NON-NLS-1$
    				int errorCode = Integer.parseInt((String) info.getMap().get(
    						"error.code")); //$NON-NLS-1$
    				Integer resourceId = null;
    				if (info.getMap().get("error.rsrc") != null) { //$NON-NLS-1$
    					try {
    						resourceId = new Integer((String) info.getMap().get(
    								"error.rsrc")); //$NON-NLS-1$
    					} catch (NumberFormatException e) {
    						resourceId = null;
    					}
    				}
    				String errorText = (String) info.getMap().get("error.text"); //$NON-NLS-1$
    				if (resourceId != null) {
    					WeakReference res_wr = (WeakReference) getResources().get(
    							resourceId);
    					if (res_wr != null && res_wr.get() != null) {
    						Resource res = (Resource) res_wr.get();
    						Map resInfoMap = info.getMap();
    						// TODO we're doing this regardless of whether it's
    						// actually a change in status
    						if (res.status == Resource.RSRC_STATUS_ERROR) {
    							debug("NOTE: had to send ResourceStatus event for error even though resource's status was already error."); //$NON-NLS-1$
    						}
    						debug("Posting APP_INFO resource error to resource " //$NON-NLS-1$
    								+ resourceId + ": " + resInfoMap); //$NON-NLS-1$
    						res.postEvent(new MockResourceStatus(resourceId
    								.intValue(), res, Resource.RSRC_STATUS_ERROR,
    								resInfoMap));
    					}
    				} else
    					debug("APP_INFO error " + errorCode + ": " + errorText); //$NON-NLS-1$ //$NON-NLS-2$
    	
    				return handleApplicationError(errorCode, errorText, resourceId);
    			}
    		}
    	
    		boolean result = super.handleEvent(event);
    		// ...
    	
    		return result;
    	}
    
    	/**
    	 * SDK Bug Fix - OMG! sick bug in HMEObject that has never been noticed or
    	 * exercised since nobody has apparently attempted to do App-in-app with
    	 * {@link #createStream(String, Map)}. They forgot a "new" keyword and
    	 * auto-"fixed" the problem creating a method named LinkedHashMap that has
    	 * default method content returning null! Also, calling code won't handle
    	 * the nulls that are returned in certain circumstances, so now returning an
    	 * empty map here. Lucky for us it is public and non-final. {@inheritDoc}
    	 * 
    	 * @see com.tivo.hme.sdk.HmeObject#parseQuery(java.lang.String)
    	 */
    	public Map parseQuery(String query) {
    		Map map = new LinkedHashMap();
    		if (query == null || query.indexOf('=') == -1) {
    			return map;
    		}
    	
    		int at = 0;
    		int len = query.length();
    	
    		if (query.startsWith("?")) { //$NON-NLS-1$
    			++at;
    		}
    		do {
    			// find = and &
    			int equal = query.indexOf('=', at);
    			if (equal < 0) {
    				throw new IllegalArgumentException(
    						"invalid query (trailing key, = not found)"); //$NON-NLS-1$
    			}
    			int amp = query.indexOf('&', equal);
    			if (amp == -1) {
    				amp = query.length();
    			}
    	
    			// add the key/value
    			try {
    				String key = URLDecoder.decode(query.substring(at, equal),
    						"UTF-8"); //$NON-NLS-1$
    				String value = URLDecoder.decode(
    						query.substring(equal + 1, amp), "UTF-8"); //$NON-NLS-1$
    				map.put(key.toLowerCase(), value);
    			} catch (UnsupportedEncodingException e) {
    				e.printStackTrace();
    			}
    	
    			// advance
    			at = amp + 1;
    		} while (at < len);
    		return map;
    	}
    
    	/* END fix JDK */
    
    	/**
    	 * Override to handle additional system font ids as [id].ttf and additional
    	 * names.
    	 */
    	public Resource createTrueType(String family) {
    		// automatically handle 10.ttf - 19.ttf as ID refs
    		if (family != null && family.matches("\\d+\\.ttf")) {
    			try {
    				int id = Integer.parseInt(family.substring(0, family.length()-4));
    				if (id < 20 && id >= 10) {
    					return new TrueTypeFalseResource(this, family, id);
    				}
    				if(id >= 20) {
    					return getResource(new Integer(id));
    				}
    			} catch (Exception e) {
    			}
    		}
    		// handle specific new names
    		if (family.equals(TTF_SERIES4FONT)) {
    			return new TrueTypeFalseResource(this, family, 13);
    		} else if (family.equals(TTF_SERIES4FONTITALIC)) {
    			return new TrueTypeFalseResource(this, family, 14);
    		} else if (family.equals(TTF_SERIES4FONT3BOLD)) {
    			return new TrueTypeFalseResource(this, family, 15);
    //		} else if... {
    		} else {
    			// default behavior
    			return super.createTrueType(family);
    		}
    	}
    
    	/**
    	 * Equivalent of TrueTypeResource except accessible to this class so we can
    	 * instantiate additional names with a supplied id
    	 */
    	private static class TrueTypeFalseResource extends Resource {
    		String name;
    
    		TrueTypeFalseResource(Application app, String name, int id) {
    			super(app, id);
    			this.name = name;
    			((V49BApplication)app).debug("Created font " + name + " with id " + id);
    		}
    
    		/**
    		 * This is a thread-safe public entry point.
    		 */
    		protected void toString(StringBuffer buf) {
    			synchronized (lock) {
    				buf.append(",ttf=" + name);
    			}
    		}
    	}
    	
    	
    	private boolean waitingToInit;
    
    	@Override
    	protected void setContext(IContext context, int version) {
    		super.setContext(context, version);
    		// make accessible version of private Application class variable.
    		this.protocolVersion = version;
    		debug("Protocol Version per TiVo DVR: " + protocolVersion); //$NON-NLS-1$
    	
    		debug("attributes: " + context.getConnectionAttributes()); //$NON-NLS-1$
    		// User-Agent: TvHttpClient -my tivo (and tsn=64...3E )
    		// User-Agent: TmkHttpRequest/1.0 - from somebody on internet - old
    		// version of tivo?
    		// User-Agent=tivo.http/03/30/2003 - simulator
    		// attributes: {Cookie=id=895df24b5038b953631128d3aa994863d51da2b1,
    		// Host=192.168.0.20:7288, User-Agent=TvHttpClient, Connection=close,
    		// tsn=64...3E}
    		this.tivoID = context.getConnectionAttribute("tsn"); //$NON-NLS-1$
    		// context.getConnectionAttribute(HTTP_HEADER_TIVO_ID);
    		// tivoVersion is in DEVICE_INFO event under "version"
    		// this.tivoVersion =
    		// context.getConnectionAttribute(HTTP_HEADER_TIVO_VERSION);
    	
    	}
    
    	/**
    	 * You should override {@link #initBeforeResolutionSet(IContext)} to do your
    	 * init work while this override handles resolution details after that is
    	 * called.
    	 */
    	@Override
    	public void init(IContext context) throws Exception {
    		super.init(context);
    		setActive(true);
    		
    		// template method for subclasses to perform init activities
    		initBeforeResolutionSet(context);
    		
    		waitingToInit = true;
    		boolean changed = setPreferredResolution();
    		// will continue init on rootBoundsChanged()
    	
    		this.setActive(true);
    	
    		if (!changed || !isResolutionSupported()) {
    			waitingToInit = false;
    			showFirstPage();
    		}
    
    	}
    
    	/**
    	 * This override coordinates with the {@link #init(IContext)} override to
    	 * call {@link #showFirstPage()} at the right time. Should probably override
    	 * {@link #rootBoundsChangedBeforeFirstPage(Rectangle)} instead of this to
    	 * maintain order of calls.
    	 */
    	@Override
    	public void rootBoundsChanged(Rectangle rootBounds) {
    		super.rootBoundsChanged(rootBounds);
    		
    		// template method for subclasses to perform rootbounds activities? maybe just subclass calling super is good enough here.
    		rootBoundsChangedBeforeFirstPage(rootBounds);
    		
    		if (isResolutionSupported()) {
    			if (waitingToInit) {
    				waitingToInit = false;
    				showFirstPage();
    			}
    		}
    	}
    
    	/**
    	 * Resolution control is supported in protocol version 0.43 and SDK 1.4.1
    	 */
    	public boolean isResolutionSupported() {
    		return this.protocolVersion >= VERSION_0_43 && isSDK141();
    	}
    
    	/**
    	 * Stream Map argument is supported in protocol version 0.45 and SDK 1.4.1
    	 */
    	public boolean isStreamMapSupported() {
    		return this.protocolVersion >= VERSION_0_45 && isSDK141();
    	}
    
    	/**
    	 * Slide Remote is supported in protocol version 0.49
    	 */
    	public boolean isSlideRemoteSupported() {
    		return this.protocolVersion >= VERSION_0_49;
    	}
    
    	/**
    	 * Tries to get the version string from the com.tivo.hme.sdk.Version class
    	 * which doesn't exist in all SDK versions. Returns null if it can't find
    	 * the class.
    	 * 
    	 * @return
    	 */
    	public static String getSDKVersion() {
    		try {
    			if (Class.forName("com.tivo.hme.sdk.Version") != null) //$NON-NLS-1$
    				return Version.getVersion();
    			else
    				return null;
    		} catch (ClassNotFoundException e) {
    			return null;
    		}
    	}
    
    	public static boolean isSDK141() {
    		if (getSDKVersion() != null)
    			return getSDKVersion().startsWith("1.4.1"); //$NON-NLS-1$
    		else
    			return false;
    	}
    
    	/** a multi-SDK version of the SDK 1.4.1 method */
    	public int getSafeActionHorizontal() {
    		if (!isSDK141()) {
    			return SAFE_ACTION_H;
    		} else {
    			return super.getSafeActionHorizontal();
    		}
    	}
    
    	/** a multi-SDK version of the SDK 1.4.1 method */
    	public int getSafeTitleVertical() {
    		if (!isSDK141()) {
    			return SAFE_TITLE_V;
    		} else {
    			return super.getSafeTitleVertical();
    		}
    	}
    
    	/** a multi-SDK version of the SDK 1.4.1 method */
    	public int getSafeActionVertical() {
    		if (!isSDK141()) {
    			return SAFE_ACTION_V;
    		} else {
    			return super.getSafeActionVertical();
    		}
    	}
    
    	/** a multi-SDK version of the SDK 1.4.1 method */
    	public int getSafeTitleHorizontal() {
    		if (!isSDK141()) {
    			return SAFE_TITLE_H;
    		} else {
    			return super.getSafeTitleHorizontal();
    		}
    	}
    
    	
    	/** Standard title area for e.g. a STREAM_LOOP_* background. */
    	public Rectangle getTitleRegion() {
    		int topPad = getSafeTitleVertical();
    		int height = getSafeTitleToScrollTop()
    				- 15;
    		int leftPad = getSafeTitleHorizontal() + getSafeLogoToScrollTitle();
    		int rightPad = getSafeTitleHorizontal();
    		return new Rectangle(leftPad, topPad, getWidth()-leftPad-rightPad, height);
    	}
    	
    	/** Standard logo area for e.g. a STREAM_LOOP_* background. */
    	public Rectangle getLogoRegion() {
    		int topPad = getSafeTitleVertical();
    		int height = getSafeTitleToScrollTop()
    				- 15;
    		int leftPad = getSafeTitleHorizontal()
    				- 20;
    		int width = getSafeLogoToScrollTitle() + 10; // seems to fit, but shouldn't it be +20 to offset leftpad adjustment?
    		return new Rectangle(leftPad, topPad, width, height);
    	}
    
    	/** Standard scroll area for e.g. a STREAM_LOOP_* background. */
    	public Rectangle getScrollRegion() {
    		int bottomPad = getSafeTitleVertical()+getSafeTitleToScrollBottom();
    		int topPad = getSafeTitleVertical()+getSafeTitleToScrollTop();
    		int leftPad = getSafeTitleHorizontal();
    		int rightPad = getSafeTitleHorizontal();
    		return new Rectangle(leftPad, topPad, getWidth()-leftPad-rightPad, getHeight()-topPad-bottomPad);
    	}
    	
    	/**
    	 * Vertical Space from safeTitle bottom to bottom of scrolling area of of a
    	 * STREAM_LOOP_* background.
    	 */
    	public int getSafeTitleToScrollBottom() {
    		if (isResolutionSupported() && getCurrentResolution().getHeight() > 480) {
    			return 30;
    		} else {
    			return 15; // SD number
    		}
    	}
    
    	/**
    	 * Vertical Space from safeTitle top to top of scrolling area of a
    	 * STREAM_LOOP_* background.
    	 */
    	public int getSafeTitleToScrollTop() {
    		if (isResolutionSupported() && getCurrentResolution().getHeight() > 480) {
    			return 85 + 60;
    		} else {
    			return 95; // SD number
    		}
    	}
    	
    	/**
    	 * Horizontal space from safeTitle left to right edge of TiVo logo in the
    	 * title area of a STREAM_LOOP_* background.  Note, logo extends a little to the left of the safeTitle left.
    	 */
    	public int getSafeLogoToScrollTitle() {
    		if (isResolutionSupported() && getCurrentResolution().getHeight() > 480) {
    			return 100; // could need a diff number for 720, but this will work.
    		} else {
    			return 45;// SD number - goes past "glow"
    		}
    	}
    	
    	protected void setMaxStackDepth(int max) {
    		if (isSDK141()) {
    			super.setMaxStackDepth(max);
    		}
    	}
    
    	/**
    	 * If {@link #isResolutionSupported()}, makes sure the resolution is the
    	 * preferred one, and if it's not, calls
    	 * {@link #setReceiverResolution(com.tivo.hme.sdk.Resolution)} with the
    	 * ResolutionInfo's preferred resolution.
    	 * 
    	 * @return true if {@link #setReceiverResolution(com.tivo.hme.sdk.Resolution)} was actually called.
    	 */
    	protected boolean setPreferredResolution() {
    			/* BEGIN HME 1.4.1e only */
    			if (isResolutionSupported()) {
    				boolean noChangeNeeded = this
    						.getReceiverResolutionInfo()
    						.getPreferredRenderingResolution()
    						.equals(this.getReceiverResolutionInfo()
    								.getRenderingResolution());
    				if (noChangeNeeded) {
    	//				debugOut(this.getCurrentResolution()+" is already preferred resolution of "+this.getReceiverResolutionInfo().getAvailableRenderingResolutions());
    					return false;
    				} else {
    	//				 debugOut(this.getCurrentResolution()
    	//						 +" => "+this.getReceiverResolutionInfo().getPreferredRenderingResolution()+" of "+this.getReceiverResolutionInfo().getAvailableRenderingResolutions());
    					this.setReceiverResolution(this.getReceiverResolutionInfo()
    							.getPreferredRenderingResolution());
    					return true;
    				}
    			}
    			/* END HME 1.4.1e only */
    			return false;
    		}
    
    }
    V49BApplicationFactory.java:
    Code:
    package com.blackledge.david.tivo;
    
    import java.io.IOException;
    
    import com.tivo.hme.interfaces.IApplication;
    import com.tivo.hme.interfaces.IContext;
    import com.tivo.hme.interfaces.ILogger;
    import com.tivo.hme.sdk.Factory;
    import com.tivo.hme.sdk.IHmeProtocol;
    import com.tivo.hme.sdk.io.HmeInputStream;
    import com.tivo.hme.sdk.io.HmeOutputStream;
    
    /**
     * Factory that creates the {@link V49BApplication} to report its version to the
     * TiVo as VERSION_0_49. (V49BApplication required for createApplication
     * duplicate to have correct method access).
     */
    public class V49BApplicationFactory extends Factory {
    	/**
    	 * Override to report a different HME version by calling
    	 * {@link #supercreateApplication(IContext, int)}.
    	 */
    	@Override
    	public IApplication createApplication(IContext context)
    			throws IOException {
            int sentVersion = 
    //                IHmeProtocol.VERSION;
            		V49IHmeProtocol.VERSION_0_49;
    //        		IHmeProtocol.VERSION_0_40;
            
    		IApplication application = supercreateApplication(context, sentVersion);
    		
    		return application;
    	}
    	
    	/**
    	 * Copy of super factory implementation with parameterized version number to
    	 * send to TiVo.
    	 */
        public IApplication supercreateApplication(IContext context, int sentVersion) throws IOException
        {
    	int version = -1;
            HmeOutputStream out = null;
            HmeInputStream in = null;
    
            synchronized (lock) {                   // lock this factory
                if (context.getOutputStream() instanceof HmeOutputStream) {
                    out = (HmeOutputStream)context.getOutputStream();
    	    }
                else {
                    out = new HmeOutputStream(context.getOutputStream());
    	    }
                
                if (context.getInputStream() instanceof HmeInputStream) {
                    in = (HmeInputStream)context.getInputStream();
                } else {
                    in = new HmeInputStream(context.getInputStream());
    	    }
                
                // HME protocol starts here
    	    // Synchronize and flush the writes, so that no other writer 
    	    // interleaves within our data
    	    synchronized (out.getMonitor()) {
                    out.writeInt(IHmeProtocol.MAGIC);
                    out.writeInt(
    //                		IHmeProtocol.VERSION
                    		sentVersion
                    		);
                    out.flush();
    	    }
    		
                // read magic and version
                int magic = in.readInt();
                if (magic != IHmeProtocol.MAGIC) {
                    throw new IOException(
    		    "bad magic: 0x" + Integer.toHexString(magic)
    //		    +getIdForException());
    		    );
                }
                version = in.readInt();
                if (version >> 8 < 
    //            IHmeProtocol.VERSION
                sentVersion
                >> 8 ) {
                    throw new IOException(
                            "version mismatch: " + 
                            ( version >> 8 ) + "." + ( version & 0xff ) +
                            " < " +
                            ( 
    //                        		IHmeProtocol.VERSION
                            		sentVersion
                            		>> 8 ) + "." + ( 
    //                        				IHmeProtocol.VERSION
                            				sentVersion
                            				& 0xff )
    //			+ getIdForException());
                            );
                }
            } // synchronized
    
            // maintain proper order of locks -- we have released 
            // the factory monitor before entering the app monitor
        
            IApplication retApp = null;
            try {
    //            Application app = (Application)clazz.newInstance();
            	// have to have local class to access setContext
            	V49BApplication app = (V49BApplication)clazz.newInstance();
                synchronized (app) {
                    app.setFactory(this);
                    app.setContext(context, version);
    		app.setMonitor(app);
                    retApp = app;
                }
            } catch (InstantiationException ex) {
    	    log(ILogger.LOG_NOTICE, ex);
            } catch (IllegalAccessException ex) {
    	    log(ILogger.LOG_NOTICE, ex);
            }
    
    	out.flush();
    
            return retApp;
        } // createApplication()
    	
    }
     
  9. Dec 2, 2013 #69 of 165
    davidblackledge

    davidblackledge Registered lÜser

    466
    0
    Sep 9, 2008
    NM
    After the overwhelming lack of interest in the last update, I just went forward with the cheapest host I could come up with for a beta of a public server.

    "Add a server" or "Add an app" with IP 212.1.209.140

    This will add Enter Webz in server mode. This means the Apps group drives what specific links or groups show up as "apps" at the top level on your TiVo.

    The default thumztaks give you a lot of top-level Apps. They use genres so in Series 4+ HD UIs, some Apps are in the music and photos section while other entries are in the apps section. Default icons are also based on genres.
    • music: Shoutcast Radio Stations
    • music: Live Concerts at archive.org
    • music: M&#8203;ore/ (last.fm, Audio Books, MP3tunes)&#8203;
    • photos: Comics & LOLz/
    • photos: Flickr
    • videos: Feature Films at archive.org
    • videos: YouTube mobile
    • videos: &#8203;More/ (Classic TV, Amazon download launch, late night lineups...)
    • Facebook
    • Twitter &#8203; -&#8203; &#8203;@&#8203;TiVo &#8203; (won't allow connection??)&#8203;
    • Email, Finance, etc./ (Google mail / voice, Mapquest, E*Trade...)
    • Sports & Weather/
    • &#8203;Shopping/ (Amazon, Ebay...)
    As a bonus, the server also has my Solitaire, Freecell, and Gene Wadleigh's Mahjongg.


    THIS IS STILL A BETA - If you wander to random websites it WILL reboot your TiVo in all likelihood, but the default listed ones seem to work well. This problem won't change in the future, but I hope to put warnings and safeguards up.
    Don't be surprised if it goes down or gets slow if this somehow becomes popular. If I find the need to upgrade the server, the IP Address WILL CHANGE.
    And in general, there's still a lot to do to have this feel vaguely like quality.


    Today's version includes image size and textarea bug fixes (necessary for e.g. Facebook and twitter status updates)

    One problem: it looks like Twitter blocks my host - I'm guessing spammers must have set up shop there, too - so Twitter just times out and never connects. I'll try to contact them to make an exception for my IP, I guess.

    Note the "Amazon download launch" mentioned above - that's the "Amazon: Your Video Library" page to initiate a push from an old Amazon video purchase to one of your TiVos... it's not pretty, but I believe it's functional.
     
  10. Dec 2, 2013 #70 of 165
    Fofer

    Fofer XenForo Rocks! TCF Club

    82,090
    286
    Oct 29, 2000
    Sounds great! Can't wait to try it when I get back home!
     
  11. Dec 2, 2013 #71 of 165
    dadrepus

    dadrepus Member

    113
    0
    Jan 4, 2012
    Columbia, MD
    Just tried it. Only tried established programs/games but EVERYTHING worked as promised. From my Mac install to boot! YEA. Can't wait to see what more you have in mind for this clever Application.
    thanks for continuing to pursue this.
     
  12. Dec 2, 2013 #72 of 165
    jgametest

    jgametest New Member

    117
    0
    Oct 30, 2013
    Sent David a PM via his email, there is much potential for what his app will bring to the community now that it's being hosted live via IP. It kind of picks up where apps.tv left off but in a whole new direction of hope. I would imagine once the Opera SDK gets off then more opportunity will arise. Now if only I can get his browser to talk to my Plex server I would be very happy. I forced my Plex to use the Opera browser in TiVo through KMTTG and it did it even let me sign into it but would not let me navigate anywhere. I will have to try this idea again this time using Enterthewebz.:p
     
  13. Dec 2, 2013 #73 of 165
    Fofer

    Fofer XenForo Rocks! TCF Club

    82,090
    286
    Oct 29, 2000
    "Mac install?" Why is that needed, now that it is hosted live via IP? I thought the whole point of that upgrade is that we don't need any software installed (or server hosted) on our LAN.
     
  14. Dec 2, 2013 #74 of 165
    wmcbrine

    wmcbrine Ziphead

    10,364
    22
    Aug 2, 2003
    1. How do you do the purple box around each menu item?
    2. How can I turn that off?
     
  15. Dec 2, 2013 #75 of 165
    jgametest

    jgametest New Member

    117
    0
    Oct 30, 2013
    With Winamp closing it's doors at the end of the month it's unsure what will happen to the @Shoutcast servers. I sent a tweet to the NY office to find out the details apparently many companies rely on this feed of Mp3's.

    With IP binded I found new choices in my Music category: More music, Shoutcast, and Concerts.

    On a different note, the TiVo played POP & Seasonal very clear. =)
    Lookout Pandora more options are coming, the same threat Live365 felt when Pandora arrived.
     
  16. Dec 2, 2013 #76 of 165
    davidblackledge

    davidblackledge Registered lÜser

    466
    0
    Sep 9, 2008
    NM
    Yeah, that was kind of my thought, too!
    Well, here's where we run into the downside of a public server. Your Plex server is on your home network, behind a firewall that the Enter Webz public server can't see through. Same issue with pyTivo running on your home network.

    I plan to make a diagram and a simple website for Enter Webz to explain this kind of stuff.
     
  17. Dec 2, 2013 #77 of 165
    davidblackledge

    davidblackledge Registered lÜser

    466
    0
    Sep 9, 2008
    NM
    You are correct... I think dadrepus was just excited because we could never get the download to work on his Mac ;]
     
  18. Dec 2, 2013 #78 of 165
    davidblackledge

    davidblackledge Registered lÜser

    466
    0
    Sep 9, 2008
    NM
    I love to surprise wmcbrine ;]

    It ends up, the HDUI handles app icons differently than they used to.
    An incorrectly sized icon on Series 3 either shows nothing or just centers and crops to size.
    The HDUI tries to show the whole thing aligned to the upper left corner, regardless of the size. This makes for some messy results for some websites where Enter Webz grabs an iPad-style icon that's very large, but inspired me to do this trick.

    The purple box is an experimental Premiere-(and Roamio?)-only icon set sized carefully to fit the App title with a transparent area to let the title show through.

    I'll eventually replace that with either a normal icon or a more subtle use of the trick.

    It only has that for "other" genre thumztaks (without their own icon) and groups, so you can "turn it off" by deleting those entries or moving them to the video genre.

    I also intend to eventually make the thumztak icon user-editable so you can set it if it's blank, or fix it if it's an oversized one. That doesn't include groups, though.
     
  19. Dec 3, 2013 #79 of 165
    jgametest

    jgametest New Member

    117
    0
    Oct 30, 2013
    There is an open player called flowplayer it makes flash available from your own site. Could this be adopted into Webz or does it need the Opera SDK active for use?:cool:

    flowplayerDOTorg <-- replace dot with a .
     
  20. Dec 3, 2013 #80 of 165
    davidblackledge

    davidblackledge Registered lÜser

    466
    0
    Sep 9, 2008
    NM
    Well, Enter Webz won't ever run Flash. But the rest of that site uses the HTML5 VIDEO tag which Enter Webz does handle. A VIDEO tag with the right format of video is shown in Enter Webz with either a "poster" image or (in the case of that site) a default video icon.

    The videos on that site appear to be an incompatible format of mp4, but other than that, it "works."
     

Share This Page