001: /*
002: * Copyright (c) 2007, Sun Microsystems, Inc.
003: *
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * * Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: * * Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: * * Neither the name of Sun Microsystems, Inc. nor the names of its
017: * contributors may be used to endorse or promote products derived
018: * from this software without specific prior written permission.
019: *
020: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
023: * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
024: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031: */
032: package example.mmademo;
033:
034: import java.util.*;
035: import java.io.*;
036: import javax.microedition.midlet.*;
037: import javax.microedition.lcdui.*;
038: import javax.microedition.media.*;
039: import javax.microedition.media.control.*;
040:
041: /**
042: * MMAPI player main window for media files, implemented as a Canvas
043: *
044: * @version 1.7
045: */
046: public class SimplePlayerCanvas extends Canvas implements
047: SimplePlayerGUI.Parent, Utils.Interruptable,
048: Utils.ContentHandler, Runnable {
049:
050: private static int PLAYER_TITLE_TOP = 2;
051: private static int LOGO_GAP = 2;
052: private static int SONG_TITLE_GAP = 2;
053: private static int KARAOKE_GAP = 1;
054: private static int TIME_GAP = 2;
055: private static int RATE_GAP = 2;
056: private static int STATUS_GAP = 2;
057:
058: private String title;
059: private Image logo = null;
060: private SimplePlayerGUI gui; // default: null
061: private Utils.BreadCrumbTrail parent;
062:
063: private VideoControl videoControl;
064:
065: private String status = "";
066: private String feedback = "";
067: private String fileTitle = "";
068:
069: int displayWidth = -1;
070: int displayHeight = -1;
071: int textHeight = 10;
072: int logoTop = 0;
073: int songTitleTop = 0;
074: int timeRateTop = 0;
075: int timeWidth = 0;
076: int karaokeTop = 0;
077: int karaokeHeight = 0;
078: int maxKaraokeLines = 0;
079: int feedbackTop = 0;
080: int statusTop = 0;
081:
082: int[] karaokeParams = new int[4];
083:
084: private static void debugOut(String s) {
085: Utils.debugOut("SimplePlayerCanvas: " + s);
086: }
087:
088: public SimplePlayerCanvas(String title, Utils.BreadCrumbTrail parent) {
089: super ();
090: this .parent = parent;
091: this .title = title;
092: }
093:
094: // //////////////////////////// interface Utils.BreadCrumbTrail ///////////
095:
096: public Displayable go(Displayable d) {
097: return parent.go(d);
098: }
099:
100: public Displayable goBack() {
101: return parent.goBack();
102: }
103:
104: public Displayable replaceCurrent(Displayable d) {
105: return parent.replaceCurrent(d);
106: }
107:
108: public Displayable getCurrentDisplayable() {
109: return parent.getCurrentDisplayable();
110: }
111:
112: // ///////////////////////// interface SimplePlayerGUI.Parent /////////////
113:
114: public Utils.BreadCrumbTrail getParent() {
115: return parent;
116: }
117:
118: // called after the media is prefetched
119: public void setupDisplay() {
120: // if there is video control, change display layout
121: videoControl = gui.getVideoControl();
122: if (videoControl != null) {
123: Utils.debugOut("Initializing display mode.");
124: try {
125: videoControl.initDisplayMode(
126: VideoControl.USE_DIRECT_VIDEO, this );
127: } catch (Exception e) {
128: setFeedback(Utils.friendlyException(e));
129: videoControl = null;
130: }
131: }
132: // force recalculation of layout
133: displayHeight = -1;
134: updateDisplay();
135: }
136:
137: public String getTitle() {
138: return title;
139: }
140:
141: public void setStatus(String s) {
142: status = s;
143: repaint(0, statusTop, displayWidth, textHeight);
144: serviceRepaints();
145: }
146:
147: public void setFeedback(String s) {
148: feedback = s;
149: repaint(0, feedbackTop, displayWidth, textHeight);
150: serviceRepaints();
151: }
152:
153: public void setFileTitle(String s) {
154: fileTitle = s;
155: repaint(0, songTitleTop, displayWidth, textHeight);
156: serviceRepaints();
157: }
158:
159: public void updateKaraoke() {
160: repaint(0, karaokeTop, displayWidth, karaokeHeight);
161: serviceRepaints();
162: }
163:
164: public void updateTime() {
165: repaint(0, timeRateTop, timeWidth, textHeight);
166: serviceRepaints();
167: }
168:
169: public void updateRate() {
170: repaint(timeWidth, timeRateTop, displayWidth, textHeight);
171: serviceRepaints();
172: }
173:
174: public void updateDisplay() {
175: repaint();
176: serviceRepaints();
177: }
178:
179: public void fullScreen(boolean value) {
180: // may not display the other items
181: // when going back to small video
182: repaint();
183: }
184:
185: // ////////////////////////////// interface Utils.ContentHandler //////////
186:
187: public synchronized void close() {
188: if (gui != null) {
189: gui.closePlayer();
190: gui = null;
191: }
192: }
193:
194: public boolean canHandle(String url) {
195: return true;
196: }
197:
198: public void handle(String name, String url) {
199: Utils.debugOut("SimplePlayerCanvas: handle " + url);
200: getGUI().setParent(this );
201: gui.setSong(name, url);
202: doHandle();
203: }
204:
205: public void handle(String name, InputStream is, String contentType) {
206: getGUI().setParent(this );
207: gui.setSong(name, is, contentType);
208: doHandle();
209: }
210:
211: public void handle(String name, Player player) {
212: getGUI().setParent(this );
213: gui.setSong(name, player);
214: doHandle();
215: }
216:
217: // ///////////////////////// interface Utils.ContentHandler //////////////// //
218:
219: private synchronized SimplePlayerGUI getGUI() {
220: if (gui == null) {
221: gui = new SimplePlayerGUI();
222: gui.initialize(title, this );
223: }
224: return gui;
225: }
226:
227: private void doHandle() {
228: // IMPL NOTE:
229: // I want to display the player first, and THEN start prefetching.
230: // the only way I was able to achieve this was by creating a new thread.
231: repaint();
232: serviceRepaints();
233: new Thread(this ).start();
234: }
235:
236: public void run() {
237: gui.startPlayer();
238: }
239:
240: // ///////////////////////// Canvas callbacks ///////////////////////////////
241:
242: protected void keyPressed(int keycode) {
243: try {
244: SimplePlayerGUI gui = getGUI();
245: switch (keycode) {
246: case KEY_NUM1:
247: // Jump backward
248: gui.skip(true);
249: break;
250: case KEY_NUM2:
251: gui.togglePlayer();
252: break;
253: case KEY_NUM3:
254: // Jump forward
255: gui.skip(false);
256: break;
257: case KEY_NUM7:
258: gui.stepFrame(-1);
259: break;
260: case KEY_NUM9:
261: gui.stepFrame(+1);
262: break;
263: case KEY_NUM5:
264: gui.pausePlayer();
265: gui.setMediaTime(0);
266: setFeedback("Player Stopped.");
267: break;
268: case KEY_NUM4:
269: gui.changeRate(true);
270: break;
271: case KEY_NUM8:
272: gui.toggleFullScreen();
273: break;
274: case KEY_NUM6:
275: gui.changeRate(false);
276: break;
277: case KEY_NUM0:
278: gui.toggleMute();
279: break;
280: case KEY_STAR:
281: gui.changeVolume(true);
282: break;
283: case KEY_POUND:
284: gui.changeVolume(false);
285: break;
286: default:
287: int code = getGameAction(keycode);
288: if (code == RIGHT) {
289: // Jump forward
290: gui.skip(false);
291: } else if (code == LEFT) {
292: // Jump backward
293: gui.skip(true);
294: } else if (code == UP) {
295: gui.transpose(false);
296: } else if (code == DOWN) {
297: gui.transpose(true);
298: } else if (code == FIRE) {
299: gui.togglePlayer();
300: }
301: }
302: } catch (Throwable t) {
303: Utils.error(t, parent);
304: }
305: }
306:
307: private boolean intersects(int clipY, int clipHeight, int y, int h) {
308: return (clipY <= y + h && clipY + clipHeight >= y);
309: }
310:
311: public void paint(Graphics g) {
312: try {
313: if (displayHeight == -1) {
314: displayWidth = getWidth();
315: displayHeight = getHeight();
316: textHeight = g.getFont().getHeight();
317: if (gui != null && videoControl == null) {
318: if (logo == null) {
319: logo = gui.getLogo();
320: }
321: } else {
322: logo = null;
323: }
324: int currTop = PLAYER_TITLE_TOP + textHeight;
325: if (logo != null) {
326: currTop += LOGO_GAP;
327: logoTop = currTop;
328: currTop += logo.getHeight();
329: }
330: currTop += SONG_TITLE_GAP;
331: songTitleTop = currTop;
332: currTop += TIME_GAP + textHeight;
333: timeRateTop = currTop;
334: timeWidth = g.getFont().stringWidth("0:00:0 ");
335: currTop += textHeight + KARAOKE_GAP;
336:
337: // feedback: before-last line
338: feedbackTop = displayHeight - 2 * textHeight
339: - STATUS_GAP;
340: // karaoke: squeeze as many lines as possible in between rate and feedback
341: maxKaraokeLines = (feedbackTop - currTop)
342: / (textHeight + KARAOKE_GAP);
343: karaokeHeight = maxKaraokeLines
344: * (textHeight + KARAOKE_GAP);
345: karaokeTop = currTop
346: + ((feedbackTop - currTop - karaokeHeight) / 2);
347: // video: same space as karaoke.
348: if (videoControl != null) {
349: int videoTop = timeRateTop + textHeight;
350: int dispWidth = videoControl.getSourceWidth();
351: int dispHeight = videoControl.getSourceHeight();
352: if (dispWidth > displayWidth)
353: dispWidth = displayWidth;
354: if (dispHeight > feedbackTop - videoTop)
355: dispHeight = feedbackTop - videoTop;
356: videoControl.setDisplayLocation(
357: (displayWidth - dispWidth) / 2, videoTop);
358: videoControl.setDisplaySize(dispWidth, dispHeight);
359: videoControl.setVisible(true);
360: Utils.debugOut("Setting video to visible at (0, "
361: + videoTop + ") with size (" + displayWidth
362: + ", " + (feedbackTop - videoTop) + ")");
363: }
364: // status: last line.
365: statusTop = displayHeight - textHeight;
366: }
367:
368: int clipX = g.getClipX();
369: int clipY = g.getClipY();
370: int clipWidth = g.getClipWidth();
371: int clipHeight = g.getClipHeight();
372: // background
373: g.setColor(0);
374: g.fillRect(clipX, clipY, clipWidth, clipHeight);
375:
376: if ((videoControl == null) || !gui.isFullScreen()) {
377: // title
378: if (intersects(clipY, clipHeight, PLAYER_TITLE_TOP,
379: textHeight)) {
380: g.setColor(0xFF7f00);
381: g.drawString(title, displayWidth >> 1,
382: PLAYER_TITLE_TOP, Graphics.TOP
383: | Graphics.HCENTER);
384: }
385: // logo
386: if (logo != null
387: && intersects(clipY, clipHeight, logoTop, logo
388: .getHeight())) {
389: g.drawImage(logo, displayWidth / 2, logoTop,
390: Graphics.TOP | Graphics.HCENTER);
391: }
392: // song name (+ duration)
393: if (intersects(clipY, clipHeight, songTitleTop,
394: textHeight)) {
395: g.setColor(0xFF7F00);
396: g.drawString(fileTitle, displayWidth >> 1,
397: songTitleTop, Graphics.TOP
398: | Graphics.HCENTER);
399: }
400: if (gui != null) {
401: // time and rate/tempo display
402: if (intersects(clipY, clipHeight, timeRateTop,
403: textHeight)) {
404: if (intersects(clipX, clipWidth, 0, timeWidth)) {
405: g.setColor(0xF0F0F0);
406: g.drawString(gui.getMediaTimeStr(), 0,
407: timeRateTop, Graphics.TOP
408: | Graphics.LEFT);
409: }
410: if (intersects(clipX, clipWidth, timeWidth + 1,
411: displayWidth)) {
412: // tempo/rate display
413: if (gui.hasTempoControl()) {
414: g.setColor(0xF0F0F0);
415: g.drawString(gui.getTempoStr(),
416: displayWidth, timeRateTop,
417: Graphics.TOP | Graphics.RIGHT);
418: } else {
419: g.setColor(0xF0F0F0);
420: g.drawString(gui.getRateStr(),
421: displayWidth, timeRateTop,
422: Graphics.TOP | Graphics.RIGHT);
423: }
424: }
425: }
426: // Karaoke text
427: if (intersects(clipY, clipHeight, karaokeTop,
428: karaokeHeight)) {
429: String[] lines = gui
430: .getKaraokeStr(karaokeParams);
431: int currTop = karaokeTop;
432: int currLine = karaokeParams[SimplePlayerGUI.KARAOKE_LINE];
433: int lineCount = karaokeParams[SimplePlayerGUI.KARAOKE_LINE_COUNT];
434: int this Line = 0;
435: if (lineCount > maxKaraokeLines) {
436: this Line = currLine - 1;
437: if (this Line < 0) {
438: this Line = 0;
439: }
440: if (this Line + maxKaraokeLines > lineCount) {
441: this Line = lineCount - maxKaraokeLines;
442: } else if (lineCount - this Line > maxKaraokeLines) {
443: lineCount = this Line + maxKaraokeLines;
444: }
445: }
446: int syllLen = karaokeParams[SimplePlayerGUI.KARAOKE_SYLLABLE_LENGTH];
447: int currLinePos = karaokeParams[SimplePlayerGUI.KARAOKE_LINE_INDEX];
448: for (; this Line < lineCount; this Line++) {
449: if (currLine != this Line || syllLen == 0) {
450: if (this Line < currLine) { // && syllLen > 0
451: // already sung text in yellow
452: g.setColor(0xFFFF30);
453: } else {
454: // other stuff in grey
455: g.setColor(0x909090);
456: }
457: g.drawString(lines[this Line], 0,
458: currTop, Graphics.TOP
459: | Graphics.LEFT);
460: } else {
461: // first draw any text before current position
462: int xPos = 0;
463: String currText;
464: if (currLinePos > 0) {
465: currText = lines[this Line]
466: .substring(0, currLinePos);
467: g.setColor(0xFFFF30); // yellow
468: g.drawString(currText, 0, currTop,
469: Graphics.TOP
470: | Graphics.LEFT);
471: xPos += g.getFont().stringWidth(
472: currText);
473: }
474: // color the current syllable
475: g.setColor(0xFFFF30);
476: currText = lines[this Line].substring(
477: currLinePos, currLinePos
478: + syllLen);
479: g.drawString(currText, xPos, currTop,
480: Graphics.TOP | Graphics.LEFT);
481: if (currLinePos + syllLen < lines[this Line]
482: .length()) {
483: xPos += g.getFont().stringWidth(
484: currText);
485: currText = lines[this Line]
486: .substring(currLinePos
487: + syllLen);
488: g.setColor(0x909090); // grey
489: g.drawString(currText, xPos,
490: currTop, Graphics.TOP
491: | Graphics.LEFT);
492: }
493: }
494: currTop += textHeight + KARAOKE_GAP;
495: }
496: }
497: }
498: // Feedback
499: if (intersects(clipY, clipHeight, feedbackTop,
500: textHeight)) {
501: g.setColor(0xE0E0FF);
502: g.drawString(feedback, 0, feedbackTop, Graphics.TOP
503: | Graphics.LEFT);
504: }
505: // Status
506: if (intersects(clipY, clipHeight, displayHeight
507: - textHeight, textHeight)) {
508: g.setColor(0xFAFAFA);
509: g.drawString(status, 0, displayHeight,
510: Graphics.BOTTOM | Graphics.LEFT);
511: }
512: }
513: } catch (Throwable t) {
514: debugOut("in paint(): " + Utils.friendlyException(t));
515: }
516: }
517:
518: /**
519: * Show a page which explains the keys.
520: * For simplicity, the page is implemented as a list...
521: */
522: public void showHelp() {
523: List list = new List("Simple Player Help", Choice.IMPLICIT);
524: list.append("1: Skip back", null);
525: list.append("2: Start/Stop", null);
526: list.append("3: Skip forward", null);
527: list.append("4: Slower", null);
528: list.append("5: Stop (and rewind)", null);
529: list.append("6: Faster", null);
530: list.append("7: Prev. video frame", null);
531: list.append("8: Fullscreen on/off", null);
532: list.append("9: Next video frame", null);
533: list.append("*: quieter", null);
534: list.append("0: Mute on/off", null);
535: list.append("#: louder", null);
536: list.append("Left: skip back", null);
537: list.append("Right: skip forward", null);
538: list.append("Up: Pitch up", null);
539: list.append("Down: Pitch down", null);
540: list.append("Fire: Start/Stop", null);
541:
542: list.addCommand(gui.backCommand);
543: list.setCommandListener(gui);
544: go(list);
545: }
546:
547: // /////////////////////////////////// Interface Utils.Interruptable ////////////////// //
548:
549: /**
550: * Called in response to a request to pause the MIDlet.
551: * This implementation will just call the same
552: * method in the GUI implementation.
553: */
554: public synchronized void pauseApp() {
555: if (gui != null) {
556: gui.pauseApp();
557: }
558: }
559:
560: /**
561: * Called when a MIDlet is asked to resume operations
562: * after a call to pauseApp(). This method is only
563: * called after pauseApp(), so it is different from
564: * MIDlet's startApp().
565: *
566: * This implementation will just call the same
567: * method in the GUI implementation.
568: */
569: public synchronized void resumeApp() {
570: if (gui != null) {
571: gui.resumeApp();
572: }
573: }
574:
575: // for debugging
576: public String toString() {
577: return "SimplePlayerCanvas";
578: }
579:
580: }
|