VideoCanvas.java

/*
 * Copyright © 2011 Nokia Corporation. All rights reserved.
 * Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation.
 * Oracle and Java are trademarks or registered trademarks of Oracle and/or its
 * affiliates. Other product and company names mentioned herein may be trademarks
 * or trade names of their respective owners.
 * See LICENSE.TXT for license information.
 */
package com.nokia.example.overlay;

//
//Source File Name:   VideoCanvas.java
/**
 * VideoCanvas displays video or camera viewfinder on Canvas
 * Drawing ControlBar on VideoControl
 * Operating VideoControl with ControlBar
 */
import java.io.IOException;
import java.io.InputStream;

import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

import javax.microedition.media.Manager;
import javax.microedition.media.MediaException;
import javax.microedition.media.Player;
import javax.microedition.media.control.VideoControl;

class VideoCanvas extends Canvas
        implements CommandListener, Runnable {

    private final VideoOverlayMIDlet midlet;
    private final Command backCommand =
            new Command("Back", Command.BACK, 0);
    private final Command captureCommand =
            new Command("Capture", Command.SCREEN, 0);
    private Player player;
    private VideoControl videoControl;
    private boolean active;
    /** If empty paint() is wanted */
    boolean emptyPaint = false;
    /** VideoCanvas type */
    private int type;
    /** ControlBar with buttons */
    private ControlBar controlBar;
    // Coordinates for moving ControlBar
    private int prev_x;
    private int prev_y;
    /** Back button location x */
    private int exitX = 0;
    /** Back button location y */
    private int exitY = 0;
    private int canvasW; // Canvas width
    private int canvasH; // Canvas height
    private int videoW;  // Video display area width
    private int videoH;  // Video display area height
    private int video_x; // VideoControl x coordinate
    private int video_y; // VideoControl y coordinate
    /** Moving is true when tap down occurs inside ControlBar area */
    private boolean moving = false;
    /**
     * Dragged is true when ControlBar is moving with pointer dragging event
     * Prevents pressing button during moving ControlBar
     */
    private boolean dragged = false;
    /** Background color of VideoCanvas */
    private final int BACKGROUND_COLOR = 0x000000;//0x33CC66;
    /** Text color of VideoCanvas */
    private final int TEXT_COLOR = 0xFFFFFF;
    /** Video path in resources */
    private final String VIDEO_LOCATOR = "/test.mp4";
    /** If ControlBar is visible */
    private boolean hideControlBar;
    /** Canvas full screen mode flag */
    private boolean fsMode;
    /**
     * Initializing VideoControl indicator
     * True until VideoControl is ready
     */
    private boolean initializingVideo = false;
    /** Initializing drawing indicator */
    private long prevRepaint = 0;
    private boolean running;
    private boolean paused = false;
    private Image back_icon;

    public void commandAction(Command c, Displayable d) {
        if (c == backCommand) {
            closingVideoCanvas();
        } else if (c == captureCommand) {
            takeSnapshot();
        }
    }
    private static final String[] INIT_STRINGS = new String[]{
        "Initializing.",
        "Initializing..",
        "Initializing...",
        "Initializing...."
    };
    private int stringIndex = 0;

    /**
     * VideoCanvas displays video or camera viewfinder on Canvas
     * Drawing ControlBar on VideoControl
     * Operating VideoControl with ControlBar
     * @param midlet - parent MIDlet
     * @param type - camera or video
     */
    public VideoCanvas(VideoOverlayMIDlet midlet, int type) {
        this.type = type;
        this.player = null;
        this.videoControl = null;
        this.active = false;
        this.running = false;
        this.paused = false;
        this.hideControlBar = false;
        this.fsMode = false;
        this.midlet = midlet;

        this.canvasW = getWidth();
        this.canvasH = getHeight();

        this.videoW = canvasW;
        this.videoH = canvasH;

        addCommand(backCommand);
        setCommandListener(this);
        this.createImages();

        this.controlBar = new ControlBar(this, 0, 0, this.videoW / 2);
    }

    /**
     * Creating images from resources for Back button
     */
    public void createImages() {
        try {
            back_icon = Image.createImage("/buttons/back.png");
        } catch (IOException ex) {
            back_icon = null;
        }
    }

    /**
     * Initializing player and setting it to videoControl
     */
    public void initPlayer() {

        initializingVideo = true;

        // Always start player in normal mode
        fsMode = false;
        this.setFullScreenMode(fsMode);
        try {
            if (this.type == VideoOverlayMIDlet.CAMERA) {
                player = Manager.createPlayer("capture://video");
            } else {
                InputStream is = this.getClass().getResourceAsStream(
                        this.VIDEO_LOCATOR);
                player = Manager.createPlayer(is,
                        this.VIDEO_LOCATOR.substring(
                        this.VIDEO_LOCATOR.lastIndexOf('.') + 1));
            }
            player.realize();
            player.prefetch();

            videoControl = (VideoControl) player.getControl("VideoControl");

            if (videoControl != null) {
                videoControl.initDisplayMode(
                        VideoControl.USE_DIRECT_VIDEO, this);
                videoControl.setDisplayFullScreen(true);

                if (this.type == VideoOverlayMIDlet.CAMERA) {
                    addCommand(captureCommand);
                } else {
                    player.setLoopCount(-1);
                }
                this.controlBar.setX(videoControl.getDisplayX());
                this.controlBar.setY(videoControl.getDisplayY());

                videoControl.setVisible(true);

                player.start();

                // Empty paint is called to ensure
                // that videoControl area is clear
                emptyPaint = true;
                repaint();
                serviceRepaints();
                emptyPaint = false;
            }
        } catch (IOException ioe) {
            discardPlayer();
            System.out.println(ioe.getMessage());
            Display.getDisplay(this.midlet).setCurrent(
                    new Alert("IOException:", ioe.getMessage(),
                    null, AlertType.ERROR));

        } catch (MediaException me) {
            discardPlayer();
            System.out.println(me.getMessage());
            Display.getDisplay(this.midlet).setCurrent(
                    new Alert("MediaException:", me.getMessage(),
                    null, AlertType.ERROR));

        } catch (SecurityException se) {
            discardPlayer();
            System.out.println(se.getMessage());
            Display.getDisplay(this.midlet).setCurrent(
                    new Alert("SecurityException", se.getMessage(),
                    null, AlertType.ERROR));

        } catch (Exception e) {
            discardPlayer();
            System.out.println(e.getMessage());
            Display.getDisplay(this.midlet).setCurrent(
                    new Alert("Exception", e.getMessage(),
                    null, AlertType.ERROR));

        } finally {
            initializingVideo = false;
        }
    }

    /**
     * discardPlayer()
     * Closing player and setting player and videoControl to null
     */
    private void discardPlayer() {
        if (player != null) {
            player.close();
            player = null;
        }
        videoControl = null;
    }

    public void paint(Graphics g) {
        if (emptyPaint == true) {
            // Do not draw anything
            // Ensure that videoControl area is clear
            return;

        }

        int state = Player.UNREALIZED;
        if (player != null) {
            state = player.getState();
        }

        if (initializingVideo) {

            paintInitString(g);

        } else {

            switch (state) {
                case Player.PREFETCHED:
                    paintOverlay(g);
                    break;
                case Player.STARTED:
                    paintOverlay(g);
                    break;
                case Player.CLOSED:
                    paintString("Closing...", g);
                    break;
                case Player.UNREALIZED:
                    break;
                case Player.REALIZED:
                    break;
                default:
                    g.setColor(BACKGROUND_COLOR);
                    g.fillRect(0, 0, getWidth(), getHeight());
                    paintString("Closing...", g);
                    break;
            }
        }
        prevRepaint = System.currentTimeMillis();

    }

    private void paintInitString(Graphics g) {
        long currentTime = System.currentTimeMillis();
        if (currentTime - prevRepaint > 100) {
            ++stringIndex;
            if (stringIndex >= INIT_STRINGS.length) {
                stringIndex = 0;
            }
        }
        paintString(INIT_STRINGS[stringIndex], g);
    }

    private void paintString(String str, Graphics g) {
        Font f = Font.getDefaultFont();
        g.setColor(BACKGROUND_COLOR);
        g.fillRect(0, 0, getWidth(), getHeight());
        g.setColor(TEXT_COLOR);
        g.setFont(f);
        g.drawString(str, getWidth() / 2, getHeight() / 2,
                Graphics.BASELINE | Graphics.HCENTER);

    }

    /**
     * Drawing content on top of MMAPI content
     * @param g - Graphics object from the paint event
     */
    private void paintOverlay(Graphics g) {

        // Check if canvas changes (e.g., in screen rotation)
        if (canvasW != getWidth()
                || canvasH != getHeight()) {
            canvasW = getWidth();
            canvasH = getHeight();
            // Setting new location
            this.controlBar.setX(videoControl.getDisplayX());
            this.controlBar.setY(videoControl.getDisplayY());
        }
        video_x = videoControl.getDisplayX();
        video_y = videoControl.getDisplayY();
        videoW = videoControl.getDisplayWidth();
        videoH = videoControl.getDisplayHeight();

        // Clear areas outside the videoControl
        g.setColor(BACKGROUND_COLOR);
        g.fillRect(0, 0, canvasW, video_y);
        g.fillRect(0, video_y + videoH, canvasW, canvasH);
        g.fillRect(0, 0, video_x, videoH);
        g.fillRect(video_x + videoW, 0, canvasW, canvasH);

        // Stop painting if clearing flag is up
        if (hideControlBar) {
            return;
        }

        // Setting ControlBar boundaries
        // Height is fixed size
        controlBar.setWidth(videoW / 2);

        // Control Bar
        this.drawButtons(g);

        // In fullscreen mode, drawing exit button
        if (fsMode) {
            // Portrait
            if (canvasH > canvasW) {
                exitX = videoW - (videoW / 3);
                exitY = canvasH - (back_icon.getHeight() + 20);
            } // Landscape
            else {
                exitX = videoW;
                exitY = videoH - (back_icon.getHeight() + 20);
            }

            g.drawImage(back_icon,
                    (exitX + canvasW) / 2, exitY,
                    Graphics.TOP | Graphics.HCENTER);
        }
    }

    /** drawButtons(Graphics g)
     * Drawing ControlBar with buttons
     * @param g - Graphics object from the paint event
     */
    private void drawButtons(Graphics g) {
        controlBar.drawControlBar(g);
    }

    public boolean isFullScreen() {
        return fsMode;
    }

    public int getType() {
        return this.type;
    }

    public int getPlayerState() {
        return this.player.getState();
    }

    /**
     * Initializing Player for VideoControl in own thread
     * Calling initPlayer to create Player
     */
    synchronized void startCanvas(int type) {
        this.controlBar.setButton_type(type);
        new Thread(new Runnable() {

            public void run() {
                initPlayer();
            }
        }).start();
    }

    /**
     * Starting thread again if it is stopped
     * Happens also the first time Canvas is displayed
     */
    synchronized void start() {
        if (!running) {
            Thread thread = new Thread(this);
            thread.start();
        }
    }

    /**
     * Stopping player and hiding VideoControl
     * This happens when Canvas is not current or it is destroyed
     */
    synchronized void stop() {
        if (player != null && active) {
            try {
                videoControl.setVisible(false);
                player.stop();
            } catch (MediaException me) {
                System.out.println(me.getMessage());
                Display.getDisplay(this.midlet).setCurrent(
                        new Alert("MediaException:", me.getMessage(),
                        null, AlertType.ERROR));
            }
            active = false;
        }

    }

    /**
     * closingVideoCanvas()
     * Player is stopped and discarded
     * Full screen mode is set to false
     * Commands are removed
     */
    private void closingVideoCanvas() {
        if (this.running == true) {
            this.running = false;
        }
        fsMode = false;
        this.stop();
        this.discardPlayer();
        if (this.type == VideoOverlayMIDlet.CAMERA) {
            removeCommand(captureCommand);
        }
        midlet.videoCanvasExit();
    }

    /**
     * takeSnapshot()
     * Taking the snapshot:
     * - Encoding is PNG with videoControl width and height
     * - Setting image to DisplayCanvas
     */
    private void takeSnapshot() {
        if (player != null) {
            try {
                byte pngImage[] =
                        videoControl.getSnapshot(
                        "encoding=png&width=" + videoControl.getDisplayWidth()
                        + "&height=" + videoControl.getDisplayHeight());
                player.stop();
                midlet.cameraCanvasCaptured(pngImage);
            } catch (MediaException me) {
                System.out.println(me.getMessage());
                Display.getDisplay(this.midlet).setCurrent(
                        new Alert("MediaException:", me.getMessage(),
                        null, AlertType.ERROR));
            } catch (Exception e) {
                System.out.println(e.getMessage());
                Display.getDisplay(this.midlet).setCurrent(
                        new Alert("Exception:", e.getMessage(),
                        null, AlertType.ERROR));
            }
        }
    }

    /**
     * togglePlayer()
     * Toggling the player: paused -> play -> paused
     */
    public void togglePlayer() {
        if (player != null) {
            if (player.getState() == Player.STARTED) {
                pausePlayer();
            } else {
                startPlayer();
            }
            repaint();
        } else {
            startPlayer();
        }
    }

    /**
     * startPlayer()
     * Starting the player -> active = true
     */
    public void startPlayer() {
        if (player != null && !active) {
            try {
                videoControl.setVisible(true);
                player.start();
                repaint();
            } catch (MediaException me) {
                System.out.println(me.getMessage());
                Display.getDisplay(this.midlet).setCurrent(
                        new Alert("Media exception:", me.getMessage(),
                        null, AlertType.ERROR));
            } catch (SecurityException se) {
                System.out.println(se.getMessage());
                Display.getDisplay(this.midlet).setCurrent(
                        new Alert("SecurityException", se.getMessage(),
                        null, AlertType.ERROR));
            }
            active = true;
        }
    }

    /**
     * pausePlayer()
     * Pausing the player -> active = false
     */
    public void pausePlayer() {
        if (player != null) {
            try {
                player.stop();
            } catch (MediaException me) {
            }
            active = false;
        }
    }

    /**
     * pointerReleased(int x, int y)
     * Pointer events in ControlBar
     */
    protected void pointerReleased(int x, int y) {
        if (player == null) {
            return;
        }
        moving = false;
        if (dragged) {
            dragged = false;
            return;
        }
        if (hideControlBar) {
            hideControlBar = false;
            repaint();
            return;
        }

        int CB_H = this.controlBar.getHeight();
        int CB_W = this.controlBar.getWidth();
        int CB_Y = this.controlBar.getY();
        int CB_X = this.controlBar.getX();

        // Snap or Play/Stop, Show and Hide
        if (y > CB_Y
                && y < CB_Y + CB_H
                && x > CB_X
                && x < CB_X + CB_W) {

            if (x > CB_X && x < CB_X + (CB_W / 3)) {
                // First button takes snapshot or starts/stops the player
                if (this.type == VideoOverlayMIDlet.CAMERA) {
                    this.takeSnapshot();
                } else {
                    this.togglePlayer();
                }
            } else if (x > CB_X + (CB_W / 3)
                    && x < CB_X + 2 * (CB_W / 3)) {
                // Second button toggles the full screen mode
                fsMode = !fsMode;
                this.setFullScreenMode(fsMode);

            } else if (x > CB_X + 2 * (CB_W / 3)
                    && x < CB_X + CB_W) {
                // Third button hides the ControlBar
                this.hideControlBar = true;
                repaint();

            }
            return;
        }
        // In full screen mode, exit is allowed
        if (fsMode) {
            if (y >= exitY && x > exitX) {
                // Closing video
                closingVideoCanvas();
                return;
            }
        }

    }

    protected void pointerPressed(int x, int y) {
        // If inside ControlBar, move it
        if (y > this.controlBar.getY()
                && y < this.controlBar.getY() + this.controlBar.getHeight()
                && x > this.controlBar.getX()
                && x < this.controlBar.getX() + this.controlBar.getWidth()) {
            moving = true;
            prev_x = x;
            prev_y = y;
        }
    }

    protected void pointerDragged(int x, int y) {
        // Move ControlBar
        if (moving) {
            int CB_Y = this.controlBar.getY();
            int CB_X = this.controlBar.getX();

            CB_X = CB_X + (x - prev_x);
            CB_Y = CB_Y + (y - prev_y);

            this.controlBar.setX(CB_X);
            this.controlBar.setY(CB_Y);
            prev_x = x;
            prev_y = y;
            repaint();
            dragged = true;
        }
    }

    public void resume() {
        synchronized (this) {
            if (paused) {
                this.paused = false;
            }
            this.notifyAll();
        }
    }

    public void pause() {
        synchronized (this) {
            if (!paused) {
                this.paused = true;
            }
        }
    }

    /**
     * showNotify()
     * Start thread when Canvas is displayed
     */
    protected void showNotify() {
        resume();
        start();
    }

    /**
     * hideNotify()
     * Stop thread when Canvas is hidden
     */
    protected void hideNotify() {
        pause();
        pausePlayer();
    }

    public void run() {
        this.running = true;
        while (running) {
            try {
                synchronized (this) {
                    if (paused) {
                        this.wait();
                    }
                }

                repaint();
                serviceRepaints();
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        this.running = false;
    }
}