All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codename1.impl.javase.fx.JavaFXSEPort Maven / Gradle / Ivy

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.codename1.impl.javase.fx;

import com.codename1.impl.javase.AbstractBrowserWindowSE;
import com.codename1.impl.javase.BrowserWindowFactory;
import com.codename1.impl.javase.IBrowserComponent;
import com.codename1.impl.javase.JavaSEPort;
import static com.codename1.impl.javase.JavaSEPort.checkForPermission;
import static com.codename1.impl.javase.JavaSEPort.retinaScale;
import com.codename1.io.Log;
import com.codename1.io.Util;
import com.codename1.media.AbstractMedia;
import com.codename1.media.AsyncMedia;
import com.codename1.media.Media;
import com.codename1.ui.Accessor;
import com.codename1.ui.BrowserComponent;
import com.codename1.ui.CN;
import com.codename1.ui.Component;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.Label;
import com.codename1.ui.PeerComponent;
import com.codename1.util.AsyncResource;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaPlayer.Status;
import javafx.scene.media.MediaView;
import javafx.scene.web.WebView;
import javafx.util.Duration;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

/**
 *
 * @author shannah
 */
public class JavaFXSEPort extends JavaSEPort {

    @Override
    public void init(Object m) {
        super.init(m);
        try {
            Class.forName("javafx.embed.swing.JFXPanel");
            Platform.setImplicitExit(false);
            fxExists = true;
        } catch (Throwable ex) {
        }
    }
    
    private static boolean isPlayable(String filename) {
        try {
            javafx.scene.media.Media media = new javafx.scene.media.Media(filename);
        } catch (javafx.scene.media.MediaException e) {
            if (e.getType() == javafx.scene.media.MediaException.Type.MEDIA_UNSUPPORTED) {
                return false;
            }
        }
        return true;
    }
    
    
     @Override
    public AsyncResource createMediaAsync(String uriAddress, final boolean isVideo, final Runnable onCompletion) {
        
        final AsyncResource out = new AsyncResource();
        if(!checkForPermission("android.permission.READ_PHONE_STATE", "This is required to play media")){
            out.error(new IOException("android.permission.READ_PHONE_STATE is required to play media"));
            return out;
        }
        if(!checkForPermission("android.permission.WRITE_EXTERNAL_STORAGE", "This is required to play media")){
            out.error(new IOException("android.permission.WRITE_EXTERNAL_STORAGE is required to play media"));
            return out;
        }
        
        if(uriAddress.startsWith("file:")) {
            uriAddress = unfile(uriAddress);
        }
        final String uri = uriAddress;
        if (!fxExists) {
            String msg = "This fetaure is supported from Java version 1.7.0_06, update your Java to enable this feature. This might fail on OpenJDK as well in which case you will need to install the Oracle JDK. ";
            System.out.println(msg);
            out.error(new IOException(msg));
            return out;
        }
        java.awt.Container cnt = canvas.getParent();
        while (!(cnt instanceof JFrame)) {
            cnt = cnt.getParent();
            if (cnt == null) {
                out.error(new RuntimeException("Could not find canvas.  Cannot create media"));
                return out;
            }
        }

        final java.awt.Container c = cnt;

        //final Media[] media = new Media[1];
        final Exception[] err = new Exception[1];
        final javafx.embed.swing.JFXPanel m = new CN1JFXPanel();
        //mediaContainer = m;
        Platform.runLater(new Runnable() {

            @Override
            public void run() {
                try {
                    if (uri.indexOf(':') < 0 && uri.lastIndexOf('/') == 0) {
                        String mimeType = "video/mp4";
                        new CodenameOneMediaPlayer(getResourceAsStream(getClass(), uri), mimeType, (JFrame) c, m, onCompletion, out);
                        return;
                    }

                   new CodenameOneMediaPlayer(uri, isVideo, (JFrame) c, m, onCompletion, out);
                } catch (Exception ex) {
                    out.error(ex);
                }
            }
        });
        return out;
    }
    
     /**
     * Plays the sound in the given stream
     *
     * @param stream the stream containing the media data
     * @param mimeType the type of the data in the stream
     * @param onCompletion invoked when the audio file finishes playing, may be
     * null
     * @return a handle that can be used to control the playback of the audio
     * @throws java.io.IOException if the URI access fails
     */
    @Override
    public AsyncResource createMediaAsync(final InputStream stream, final String mimeType, final Runnable onCompletion) {
        final AsyncResource out = new AsyncResource();
        if(!checkForPermission("android.permission.READ_PHONE_STATE", "This is required to play media")){
            out.error(new IOException("android.permission.READ_PHONE_STATE is required to play media"));
            return out;
        }
        if(!checkForPermission("android.permission.WRITE_EXTERNAL_STORAGE", "This is required to play media")){
             out.error(new IOException("android.permission.WRITE_EXTERNAL_STORAGE is required to play media"));
            return out;
        }
        
        if (!fxExists) {
            String msg = "This fetaure is supported from Java version 1.7.0_06, update your Java to enable this feature. This might fail on OpenJDK as well in which case you will need to install the Oracle JDK. ";
            //System.out.println(msg);
            out.error(new IOException(msg));
            return out;
        }
        java.awt.Container cnt = canvas.getParent();
        while (!(cnt instanceof JFrame)) {
            cnt = cnt.getParent();
            if (cnt == null) {
                return null;
            }
        }
        final java.awt.Container c = cnt;

        //final Media[] media = new Media[1];
        //final Exception[] err = new Exception[1];
        final javafx.embed.swing.JFXPanel m = new CN1JFXPanel();
        //mediaContainer = m;

        Platform.runLater(new Runnable() {

            @Override
            public void run() {
                try {
                    new CodenameOneMediaPlayer(stream, mimeType, (JFrame) c, m, onCompletion, out);
                } catch (Exception ex) {
                    out.error(ex);
                }
            }
        });

        return out;
    }
    
    
    class CodenameOneMediaPlayer extends AbstractMedia {
        private java.util.Timer endMediaPoller;
        private Runnable onCompletion;
        private List completionHandlers;
        private javafx.scene.media.MediaPlayer player;
//        private MediaPlayer player;
        private boolean realized = false;
        private boolean isVideo;
        private javafx.embed.swing.JFXPanel videoPanel;
        private JFrame frm;
        private boolean playing = false;
        private boolean nativePlayerMode;
        private AsyncResource _callback;
        
        /**
         * This is a callback for the JavaFX media player that is supposed to fire
         * when the media is paused.  Unfortunately this is unreliable as the status
         * events seem to stop working after the first time it is paused.  
         * We use a poller (the endMediaPoller timer) to track the status of the video
         * so that the change listeners are fired.  This really sucks!
         */
        private Runnable onPaused = new Runnable() {
            public void run() {
                if (endMediaPoller != null) {
                    endMediaPoller.cancel();
                    endMediaPoller = null;
                }
                stopEndMediaPoller();
                playing = false;

                fireMediaStateChange(AsyncMedia.State.Paused);
            }
        };
        /**
         * This is a callback for the JavaFX media player that is supposed to fire
         * when the media is paused.  Unfortunately this is unreliable as the status
         * events seem to stop working after the first time it is paused.  
         * We use a poller (the endMediaPoller timer) to track the status of the video
         * so that the change listeners are fired.  This really sucks!
         */
        private Runnable onPlaying = new Runnable() {
            @Override
            public void run() {
                playing = true;
                startEndMediaPoller();
                fireMediaStateChange(AsyncMedia.State.Playing);
            }
            
        };
        
        /**
         * This is a callback for the JavaFX media player that is supposed to fire
         * when the media is paused.  Unfortunately this is unreliable as the status
         * events seem to stop working after the first time it is paused.  
         * We use a poller (the endMediaPoller timer) to track the status of the video
         * so that the change listeners are fired.  This really sucks!
         */
        private Runnable onError = new Runnable() {
            public void run() {
                if (_callback != null && !_callback.isDone()) {
                    _callback.error(player.errorProperty().get());
                    return;
                } else {
                    Log.e(player.errorProperty().get());
                }
                fireMediaError(createMediaException(player.errorProperty().get()));
                if (!playing) {
                    stopEndMediaPoller();
                    fireMediaStateChange(AsyncMedia.State.Playing);
                    fireMediaStateChange(AsyncMedia.State.Paused);
                }

            }
        };
        
        public CodenameOneMediaPlayer(String uri, boolean isVideo, JFrame f, javafx.embed.swing.JFXPanel fx, final Runnable onCompletion, final AsyncResource callback) throws IOException {
            _callback = callback;
            if (onCompletion != null) {
                addCompletionHandler(onCompletion);
            }
            
            this.onCompletion = new Runnable() {

                @Override
                public void run() {
                    if (callback != null && !callback.isDone()) {
                        callback.complete(CodenameOneMediaPlayer.this);
                    }
                    stopEndMediaPoller();
                    playing = false;
                    
                    fireMediaStateChange(AsyncMedia.State.Paused);
                    fireCompletionHandlers();
                }
                
            };
            this.isVideo = isVideo;
            this.frm = f;
            try {
                if (uri.startsWith("file:")) {
                    uri = unfile(uri);
                }
                File fff = new File(uri);
                if(fff.exists()) {
                    uri = fff.toURI().toURL().toExternalForm();
                }
                if (isVideo && !isPlayable(uri)) {
                    // JavaFX doesn't seem to support .mov files.  But if you simply rename it
                    // to .mp4, then it will play it  (if it is mpeg4).
                    // So this will improve .mov files from failing to 100% of the time
                    // to only half of the time (when it isn't an mp4)
                    File temp = File.createTempFile("mtmp", ".mp4");
                    temp.deleteOnExit();
                    FileOutputStream out = new FileOutputStream(temp);
                    byte buf[] = new byte[1024];
                    int len = 0;
                    InputStream stream = new URL(uri).openStream();
                    while ((len = stream.read(buf, 0, buf.length)) > -1) {
                        out.write(buf, 0, len);
                    }
                    stream.close();
                    uri = temp.toURI().toURL().toExternalForm();
                }
                
                player = new MediaPlayer(new javafx.scene.media.Media(uri));
                
                player.setOnReady(new Runnable() {
                    public void run() {
                        if (callback != null && !callback.isDone()) {
                            callback.complete(CodenameOneMediaPlayer.this);
                        }
                    }
                });

                installFxCallbacks();
                if (isVideo) {
                    videoPanel = fx;
                }
                

            } catch (Exception ex) {
                if (callback != null && !callback.isDone()) {
                    callback.error(ex);
                } else {
                    ex.printStackTrace();
                    throw new RuntimeException(ex);
                }
            }
        }

        private com.codename1.media.AsyncMedia.MediaException createMediaException(javafx.scene.media.MediaException ex) {
            AsyncMedia.MediaErrorType type;
            switch (ex.getType()) {
                case MEDIA_CORRUPTED:
                    type = AsyncMedia.MediaErrorType.Decode;
                    break;
                case MEDIA_INACCESSIBLE:
                case MEDIA_UNAVAILABLE:
                    type = AsyncMedia.MediaErrorType.Network;
                    break;
                case MEDIA_UNSUPPORTED:
                    type = AsyncMedia.MediaErrorType.SrcNotSupported;
                    break;
                case MEDIA_UNSPECIFIED:
                    type = AsyncMedia.MediaErrorType.Unknown;
                    break;
                case OPERATION_UNSUPPORTED:
                    type = AsyncMedia.MediaErrorType.SrcNotSupported;
                    break;
                case PLAYBACK_ERROR:
                    type = AsyncMedia.MediaErrorType.Decode;
                    break;
                case PLAYBACK_HALTED:
                    type = AsyncMedia.MediaErrorType.Aborted;
                    break;
                //case UNKNOWN:
                default:
                    type = AsyncMedia.MediaErrorType.Unknown;
                    break;
                    
            }
            return new com.codename1.media.AsyncMedia.MediaException(type, ex);
        }
        
        /**
         * This starts a timer which checks the status of media every Xms so that it can fire
         * status change events and on completion events.  The JavaFX media player has onPlaying,
         * onPaused, etc.. status events of its own that seem to not work in many cases, so we
         * need to use this timer to poll for the status.  That really sucks!!
         */
        private void startEndMediaPoller() {
            stopEndMediaPoller();
            endMediaPoller = new java.util.Timer();
            endMediaPoller.schedule(new TimerTask() {
                @Override
                public void run() {
                    
                    // Check if the media is playing but we haven't updated our status.
                    // If so, we change our status to playing, and fire a state change event.
                    if (!playing && player.getStatus() == MediaPlayer.Status.PLAYING) {
                        Platform.runLater(new Runnable() {
                            // State tracking on the fx thread to avoid race conditions.
                            public void run() {
                                if (!playing && player.getStatus() == MediaPlayer.Status.PLAYING) {
                                    playing = true;
                                    fireMediaStateChange(AsyncMedia.State.Playing);
                                }
                            }
                            
                        });
                        
                    } else if (playing && player.getStatus() != MediaPlayer.Status.PLAYING) {
                        stopEndMediaPoller();
                        Platform.runLater(new Runnable() {
                            public void run() {
                                if (playing && player.getStatus() != MediaPlayer.Status.PLAYING) {
                                    
                                    playing = false;
                                    fireMediaStateChange(AsyncMedia.State.Paused);
                                }
                            }
                            
                        });
                    }
                    double diff = player.getTotalDuration().toMillis() - player.getCurrentTime().toMillis();
                    if (playing && diff < 0.01) {
                        Platform.runLater(new Runnable() {
                            public void run() {
                                double diff = player.getTotalDuration().toMillis() - player.getCurrentTime().toMillis();
                                if (playing && diff < 0.01) {
                                    Runnable completionCallback = CodenameOneMediaPlayer.this.onCompletion;
                                    if (completionCallback != null) {
                                        completionCallback.run();
                                    }
                                }
                            }
                        });
                    }
                }

            }, 100, 100);
        }
        
        /**
         * Stop the media state poller. This is called when the media is paused.
         */
        private void stopEndMediaPoller() {
            if (endMediaPoller != null) {
                endMediaPoller.cancel();
                endMediaPoller = null;
            }
        }
        
        public CodenameOneMediaPlayer(InputStream stream, String mimeType, JFrame f, javafx.embed.swing.JFXPanel fx, final Runnable onCompletion, final AsyncResource callback) throws IOException {
            String suffix = guessSuffixForMimetype(mimeType);

            File temp = File.createTempFile("mtmp", suffix);
            temp.deleteOnExit();
            FileOutputStream out = new FileOutputStream(temp);
            byte buf[] = new byte[1024];
            int len = 0;
            while ((len = stream.read(buf, 0, buf.length)) > -1) {
                out.write(buf, 0, len);
            }
            stream.close();

            if (onCompletion != null) {
                addCompletionHandler(onCompletion);
                
            }
            this.onCompletion = new Runnable() {

                @Override
                public void run() {
                    
                    if (callback != null && !callback.isDone()) {
                        callback.complete(CodenameOneMediaPlayer.this);
                    }
                    stopEndMediaPoller();
                    playing = false;
                    
                    fireMediaStateChange(AsyncMedia.State.Paused);
                    fireCompletionHandlers();
                }
            };
            this.isVideo = mimeType.contains("video");
            this.frm = f;
            try {
                player = new MediaPlayer(new javafx.scene.media.Media(temp.toURI().toString()));
                player.setOnReady(new Runnable() {
                    public void run() {
                        if (callback != null) {
                            callback.complete(CodenameOneMediaPlayer.this);
                        }
                    }
                });
                installFxCallbacks();
                if (isVideo) {
                    videoPanel = fx;
                }
                

            } catch (Exception ex) {
                if (callback != null) {
                    callback.error(ex);
                } else {
                    ex.printStackTrace();
                    throw new RuntimeException(ex);
                }
            }
        }

        
        private void fireCompletionHandlers() {
            if (completionHandlers != null && !completionHandlers.isEmpty()) {
                
                Display.getInstance().callSerially(new Runnable() {

                    @Override
                    public void run() {
                        if (completionHandlers != null && !completionHandlers.isEmpty()) {
                            List  toRun;

                            synchronized(CodenameOneMediaPlayer.this) {
                                toRun = new ArrayList(completionHandlers);
                            }
                            for (Runnable r : toRun) {
                                r.run();
                            }
                        }
                    }

                });
            }
        }
        
        public void addCompletionHandler(Runnable onCompletion) {
            synchronized(this) {
                if (completionHandlers == null) {
                    completionHandlers = new ArrayList();
                }

                completionHandlers.add(onCompletion);
            }
        }

        public void removeCompletionHandler(Runnable onCompletion) {
            if (completionHandlers != null) {
                synchronized(this) {
                    completionHandlers.remove(onCompletion);
                }
            }
        }
        
        public void cleanup() {
            pause();
        }

        public void prepare() {
        }
        
        @Override
        protected void playImpl() {
            
            if (isVideo && nativePlayerMode) {
                // To simulate native player mode, we will show a form with the player.
                final Form currForm = Display.getInstance().getCurrent();
                Form playerForm = new Form("Video Player", new com.codename1.ui.layouts.BorderLayout()) {

                    @Override
                    protected void onShow() {
                        Platform.runLater(new Runnable() {
                            public void run() {
                                playInternal();
                            }

                        });
                    }
                    
                };
                com.codename1.ui.Toolbar tb = new com.codename1.ui.Toolbar();
                playerForm.setToolbar(tb);
                tb.setBackCommand("Back", new com.codename1.ui.events.ActionListener() {
                    public void actionPerformed(com.codename1.ui.events.ActionEvent e) {
                        pauseInternal();
                        currForm.showBack();
                    }
                });
                
                Component videoComponent = getVideoComponent();
                if (videoComponent.getComponentForm() != null) {
                    videoComponent.remove();
                }
                playerForm.addComponent(com.codename1.ui.layouts.BorderLayout.CENTER, videoComponent);
                playerForm.show();
                return;
                
            }
            playInternal();
            
        }

        private void playInternal() {
            
            installFxCallbacks();
            player.play();
            startEndMediaPoller();
        }
        
        private void pauseInternal() {
            player.pause();
            
            
        
        }
        
        /**
         * Installs listeners for the javafx media player.  Unfortunately these are
         * incredibly unreliable.  onPlaying only first the first time it plays.  OnPaused
         * also stops firing after the first pause.  onEndOfMedia sometimes fires but not
         * other times.  We use the endOfMediaPoller timer as a backup to test for status
         * changes.
         */
        private void installFxCallbacks() {
            
            player.setOnPlaying(onPlaying);
            player.setOnPaused(onPaused);
            player.setOnError(onError);
            player.setOnEndOfMedia(onCompletion);
           
        }
        
        @Override
        protected void pauseImpl() {
            if(player.getStatus() == Status.PLAYING) {
                pauseInternal();
            }
            //playing = false;
            
        }

        public int getTime() {
            return (int) player.getCurrentTime().toMillis();
        }

        public void setTime(final int time) {
           player.seek(new Duration(time));
            
        }

        public int getDuration() {
            int d = (int) player.getStopTime().toMillis();
            if(d == 0){
                return -1;
            }
            return d;
        }

        public void setVolume(int vol) {
            player.setVolume(((double) vol / 100d));
        }

        public int getVolume() {
            return (int) player.getVolume() * 100;
        }

        @Override
        public Component getVideoComponent() {
            if (!isVideo) {
                return new Label();
            }
            if (videoPanel != null) {
                final Component[] retVal = new Component[1];
                Platform.runLater(new Runnable() {

                    @Override
                    public void run() {
                        retVal[0] = new VideoComponent(frm, videoPanel, player);
                    }
                });
                Display.getInstance().invokeAndBlock(new Runnable() {

                    @Override
                    public void run() {
                        while (retVal[0] == null) {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException ex) {
                                Logger.getLogger(JavaSEPort.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }
                    }
                });
                return retVal[0];
            }
            System.out.println("Video Playing is not supported on this platform");
            Label l = new Label("Video");
            l.getStyle().setAlignment(Component.CENTER);
            return l;
        }

        public boolean isVideo() {
            return isVideo;
        }

        public boolean isFullScreen() {
            return false;
        }

        public void setFullScreen(boolean fullScreen) {
        }

        @Override
        public boolean isPlaying() {
            return playing;
        }

        @Override
        public void setNativePlayerMode(boolean nativePlayer) {
            nativePlayerMode = nativePlayer;
        }

        @Override
        public boolean isNativePlayerMode() {
            return nativePlayerMode;
        }

        public void setVariable(String key, Object value) {
        }

        public Object getVariable(String key) {
            return null;
        }
    }
    
    
    @Override
    public void addCompletionHandler(Media media, Runnable onCompletion) {
        super.addCompletionHandler(media, onCompletion);
        if (media instanceof CodenameOneMediaPlayer) {
            ((CodenameOneMediaPlayer)media).addCompletionHandler(onCompletion);
        }
    }

    @Override
    public void removeCompletionHandler(Media media, Runnable onCompletion) {
        super.removeCompletionHandler(media, onCompletion); 
        if (media instanceof CodenameOneMediaPlayer) {
            ((CodenameOneMediaPlayer)media).removeCompletionHandler(onCompletion);
        }
    }

     /**
     * Video peer component.
     * 
     * In contrast to the BrowserComponent and Peer (other peer components),
     * the native peer supports hi-resolution retina displays.  This was possible
     * on this component, but no browser component, or other components in general
     * because videos don't need to respond to pointer events.  Thus the rendered
     * dimensions and location (on the CN1 pipeline) may be different the size and 
     * position of the actual component on the screen (even though in all cases
     * we hide the actual component).
     */
    class VideoComponent extends PeerComponent {

        private javafx.embed.swing.JFXPanel vid;
        private JFrame frm;
        
        // Container that holds the video
        private JPanel cnt = new JPanel();
        private MediaView v;
        private boolean init = false;
        private Rectangle bounds = new Rectangle();
        
        // AWT paints to this buffered image
        // CN1 reads from the buffered image to paint in its own pipeline.
        BufferedImage buf;

        // Gets the buffered image that AWT paints to and CN1 reads from
        private BufferedImage getBuffer() {
            if (buf == null || buf.getWidth() != cnt.getWidth() || buf.getHeight() != cnt.getHeight()) {

                buf = new BufferedImage((int)(cnt.getWidth()), (int)(cnt.getHeight()), BufferedImage.TYPE_INT_ARGB);
            }
            return buf;
        }

        
        
        
        
        /**
         * Paints the native component to the buffer
         */
        private void paintOnBuffer() {
            
            // We need to synchronize on the peer component
            // drawNativePeer will also synchronize on this
            // This prevents simulataneous reads and writes to/from the
            // buffered image.
            if (EventQueue.isDispatchThread()) {
                // Only run this on the AWT event dispatch thread
                // to avoid deadlocks
                synchronized(VideoComponent.this) {
                    paintOnBufferImpl();
                }
            } else if (!Display.getInstance().isEdt()){
                // I can only imagine bad things 
                try {
                    EventQueue.invokeAndWait(new Runnable() {
                        public void run() {
                            paintOnBuffer();
                        }
                    });
                } catch (Throwable t) {
                    t.printStackTrace();
                }
            }
        }
        
        private void paintOnBufferImpl() {
            final BufferedImage buf = getBuffer();
            Graphics2D g2d = buf.createGraphics();
            AffineTransform t = g2d.getTransform();
            double tx = t.getTranslateX();
            double ty = t.getTranslateY();
            vid.paint(g2d);
            g2d.dispose();
            VideoComponent.this.putClientProperty("__buffer", buf);
        }
        
        public VideoComponent(JFrame frm, final javafx.embed.swing.JFXPanel vid, javafx.scene.media.MediaPlayer player) {
            super(null);
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    
                    
                    
                    cnt = new JPanel() {
                        
                        @Override
                        public void paint(java.awt.Graphics g) {
                            paintOnBuffer();
                            
                            // After we paint to the buffer we need to 
                            // tell CN1 to paint the buffer to its pipeline.
                            Display.getInstance().callSerially(new Runnable() {
                                public void run() {
                                    VideoComponent.this.repaint();
                                }
                            });
                            
                        }

                        @Override
                        protected void paintChildren(java.awt.Graphics g) {
                            // Not sure if this is necessary
                            // but we don't want any painting to occur
                            // on regular pipeline
                        }

                        @Override
                        protected void paintBorder(java.awt.Graphics g) {
                            // Not sure if this is necessary but we don't
                            // want any painting to occur on regular pipeline
                        }
                        
                        
                        
                    };

                    cnt.setOpaque(false);
                    vid.setOpaque(false);
                    cnt.setLayout(new BorderLayout());
                    cnt.add(BorderLayout.CENTER, vid);
                    cnt.setVisible(false);
                }
            });

            Group root = new Group();
            
            v = new MediaView(player);
            final Runnable oldOnReady = player.getOnPlaying();
            player.setOnPlaying(new Runnable() {
                public void run() {
                    if (oldOnReady != null) oldOnReady.run();
                    Display.getInstance().callSerially(new Runnable() {
                        public void run() {
                            if (VideoComponent.this.getParent() != null) {
                                VideoComponent.this.getParent().revalidate();
                            }
                        }
                    });
                }
            });
            
            root.getChildren().add(v);
            vid.setScene(new Scene(root));

            this.vid = vid;
            this.frm = frm;
            
        }

        @Override
        protected void initComponent() {
            bounds.setBounds(0,0,0,0);
            super.initComponent();
        }

        @Override
        protected void deinitialize() {
            super.deinitialize();
            if (testRecorder != null) {
                testRecorder.dispose();
                testRecorder = null;
            }
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    vid.setScene(null);
                    vid.removeAll();
                    cnt.remove(vid);
                    frm.remove(cnt);
                    frm.repaint();
                    
                    //mediaContainer = null;
                }
            });
            
        }

        protected void setLightweightMode(final boolean l) {
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {

                    if (!l) {
                        if (!init) {
                            init = true;
                            cnt.setVisible(true);
                            frm.add(cnt, 0);
                            
                            frm.repaint();
                        } else {
                            cnt.setVisible(false);
                        }
                    } else {
                        if (init) {
                            cnt.setVisible(false);
                        }
                    }
                }
            });

        }

        @Override
        protected com.codename1.ui.geom.Dimension calcPreferredSize() {
            com.codename1.ui.geom.Dimension out = new com.codename1.ui.geom.Dimension((int)(vid.getPreferredSize().width), (int)(vid.getPreferredSize().height));
            return out;
        }

        @Override
        public void paint(final Graphics g) {
            if (init) {
                
                onPositionSizeChange();
                drawNativePeer(Accessor.getNativeGraphics(g), this, cnt);
                
                EventQueue.invokeLater(new Runnable() {
                    public void run() {
                        paintOnBuffer();
                    }
                });
                
                
            }else{
                if(getComponentForm() != null && getComponentForm() == getCurrentForm()){
                    setLightweightMode(false);
                }
            }
        }
        
        
        @Override
        protected void onPositionSizeChange() {
            final int x = getAbsoluteX();
            final int y = getAbsoluteY();
            final int w = getWidth();
            final int h = getHeight();

            int screenX = 0;
            int screenY = 0;
            if(getScreenCoordinates() != null) {
                screenX = getScreenCoordinates().x;
                screenY = getScreenCoordinates().y;
            }
            
            // NOTE:  For the VideoComponent we make the size the actual
            // pixel size of the light-weight peer even though, on retina,
            // this means that the native component is twice the height and
            // width of the native peer.  We can do this here because
            // the video component doesn't have any interactivity 
            // that constrains us to keep the same real position on the screen
            // as our render position is.  THis is not the case for WebBrowser
            // 
            bounds.setBounds((int) ((x + screenX + canvas.x)),
                    (int) ((y + screenY + canvas.y)),
                    (int) (w),
                    (int) (h));
            
            if(!bounds.equals(cnt.getBounds())){
                Platform.runLater(new Runnable() {

                    @Override
                    public void run() {

                        v.setFitWidth(w );
                        v.setFitHeight(h);
                        
                        SwingUtilities.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                cnt.setBounds(bounds);
                                cnt.doLayout();
                                paintOnBuffer();
                                
                            }
                        });
                    }
                });
            }

        }
    }

    /**
     * {@inheritDoc }
     */
    @Override
    public AsyncResource captureBrowserScreenshot(PeerComponent browserPeer) {
        if (!(browserPeer instanceof SEBrowserComponent)) {
            return null;
        }
        SEBrowserComponent sebc = (SEBrowserComponent)browserPeer;
        return sebc.captureScreenshot();
    }
    
    
    
    public PeerComponent createFXBrowserComponent(final Object parent) {
        java.awt.Container cnt = canvas.getParent();
        while (!(cnt instanceof JFrame)) {
            cnt = cnt.getParent();
            if (cnt == null) {
                return null;
            }
        }
        final java.awt.Container c = cnt;

        final Exception[] err = new Exception[1];
        final javafx.embed.swing.JFXPanel webContainer = new CN1JFXPanel();
        final SEBrowserComponent[] bc = new SEBrowserComponent[1];
        
        

        final SEBrowserComponent bcc = new SEBrowserComponent();
        Platform.setImplicitExit(false);
        Platform.runLater(new Runnable() {

            @Override
            public void run() {
                StackPane root = new StackPane();
                final WebView webView = new WebView();
                
                root.getChildren().add(webView);
                webContainer.setScene(new Scene(root));
                
                // now wait for the Swing side to finish initializing f'ing JavaFX is so broken its unbeliveable
                JPanel parentPanel =  ((JPanel)canvas.getParent());
                
                bcc.SEBrowserComponent_init(
                        JavaFXSEPort.this, 
                        parentPanel, 
                        webContainer, 
                        webView, 
                        (BrowserComponent) parent, 
                        hSelector, 
                        vSelector
                );
                
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        bc[0] = bcc;
                        synchronized (bc) {
                            bc.notify();
                        }
                    }
                });
                
            }
        });
        if (bc[0] == null && err[0] == null) {

            Display.getInstance().invokeAndBlock(new Runnable() {

                @Override
                public void run() {
                    while (bc[0] == null && err[0] == null) {	
                        Util.wait(bc, 20);		
                    }
                }
            });
        }
        
        return bc[0];
    }private void browserExposeInJavaScriptImpl(final PeerComponent browserPeer, final Object o, final String name) {
        ((IBrowserComponent) browserPeer).exposeInJavaScript(o, name);
    }
    
    public void browserExposeInJavaScript(final PeerComponent browserPeer, final Object o, final String name) {
        if (!(browserPeer instanceof SEBrowserComponent)) {
            return;
        }
        if (Platform.isFxApplicationThread()) {
            browserExposeInJavaScriptImpl(browserPeer, o, name);
            return;
        }
        Platform.runLater(new Runnable() {

            @Override
            public void run() {
                browserExposeInJavaScriptImpl(browserPeer, o, name);
            }
        });
    }
    
    public PeerComponent createBrowserComponent(final Object parent) {
        boolean useWKWebView = "true".equals(Display.getInstance().getProperty("BrowserComponent.useWKWebView", "false"));
        if (useWKWebView) {
            if (!useWKWebViewChecked) {
                useWKWebViewChecked = true;
                Map m = Display.getInstance().getProjectBuildHints();
                if(m != null) {
                    if(!m.containsKey("ios.useWKWebView")) {
                        Display.getInstance().setProjectBuildHint("ios.useWKWebView", "true");
                    }
                }
            }
        }
        return createFXBrowserComponent(parent);
            
        
    }
    
    public boolean isNativeBrowserComponentSupported() {
        return fxExists && !blockNativeBrowser;
        //return false;
    }
    
    class CN1JFXPanel extends javafx.embed.swing.JFXPanel {

        @Override
        public void revalidate() {
            // We need to override this with an empty implementation to workaround
            // Deadlock bug  http://bugs.java.com/view_bug.do?bug_id=8058870
            // If we allow the default implementation, then it will periodically deadlock
            // when displaying a browser component
        }

        
        
        @Override
        protected void processMouseEvent(MouseEvent e) {
            //super.processMouseEvent(e); //To change body of generated methods, choose Tools | Templates.
            if (!sendToCn1(e)) {
                super.processMouseEvent(e);
            }
            
        }

        @Override
        protected void processMouseMotionEvent(MouseEvent e) {
            if (!sendToCn1(e)) {
                super.processMouseMotionEvent(e); //To change body of generated methods, choose Tools | Templates.
            }
            
        }

        @Override
        protected void processMouseWheelEvent(MouseWheelEvent e) {
            if (!sendToCn1(e)) {
                super.processMouseWheelEvent(e); //To change body of generated methods, choose Tools | Templates.
            }
        }


        
        
        private boolean peerGrabbedDrag=false;
        
        private boolean sendToCn1(MouseEvent e) {
            
            int cn1X = getCN1X(e);
            int cn1Y = getCN1Y(e);
            if ((!peerGrabbedDrag || true) && Display.isInitialized()) {
                Form f = Display.getInstance().getCurrent();
                if (f != null) {
                    Component cmp = f.getComponentAt(cn1X, cn1Y);
                    //if (!(cmp instanceof PeerComponent) || cn1GrabbedDrag) {
                        // It's not a peer component, so we should pass the event to the canvas
                        e = SwingUtilities.convertMouseEvent(this, e, canvas);
                        switch (e.getID()) {
                            case MouseEvent.MOUSE_CLICKED:
                                canvas.mouseClicked(e);
                                break;
                            case MouseEvent.MOUSE_DRAGGED:
                                canvas.mouseDragged(e);
                                break;
                            case MouseEvent.MOUSE_MOVED:
                                canvas.mouseMoved(e);
                                break;
                            case MouseEvent.MOUSE_PRESSED:
                                // Mouse pressed in native component - passed to lightweight cmp
                                if (!(cmp instanceof PeerComponent)) {
                                    cn1GrabbedDrag = true;
                                }
                                canvas.mousePressed(e);
                                break;
                            case MouseEvent.MOUSE_RELEASED:
                                cn1GrabbedDrag = false;
                                canvas.mouseReleased(e);
                                break;
                            case MouseEvent.MOUSE_WHEEL:
                                canvas.mouseWheelMoved((MouseWheelEvent)e);
                                break;
                                
                        }
                        //return true;
                        if (cn1GrabbedDrag) {
                            return true;
                        }
                        if (cmp instanceof PeerComponent) {
                            return false;
                        }
                        return true;
                    //}
                }
            }
            if (e.getID() == MouseEvent.MOUSE_RELEASED) {
                cn1GrabbedDrag = false;
                peerGrabbedDrag = false;
            } else if (e.getID() == MouseEvent.MOUSE_PRESSED) {
                peerGrabbedDrag = true;
            }
            return false;
        }
        
        private int getCN1X(MouseEvent e) {
            if (canvas == null) {
                int out = e.getXOnScreen();
                if (out == 0) {
                    // For some reason the web browser would return 0 for screen coordinates
                    // but would still have correct values for getX() and getY() when 
                    // dealing with mouse wheel events.  In these cases we need to 
                    // get the screen coordinate from the component
                    // and add it to the relative coordinate.
                    out = e.getX(); // In some cases absX is set to zero for mouse wheel events
                    Object source = e.getSource();
                    if (source instanceof java.awt.Component) {
                        Point pt = ((java.awt.Component)source).getLocationOnScreen();
                        out  += pt.x;
                    }
                }
                return out;
            }
            java.awt.Rectangle screenCoords = getScreenCoordinates();
            if (screenCoords == null) {
                screenCoords = new java.awt.Rectangle(0, 0, 0, 0);
            }
            int x = e.getXOnScreen();
            if (x == 0) {
                // For some reason the web browser would return 0 for screen coordinates
                // but would still have correct values for getX() and getY() when 
                // dealing with mouse wheel events.  In these cases we need to 
                // get the screen coordinate from the component
                // and add it to the relative coordinate.
                x = e.getX();
                Object source = e.getSource();
                if (source instanceof java.awt.Component) {
                    Point pt = ((java.awt.Component)source).getLocationOnScreen();
                    x += pt.x;
                }
            }
            return (int)((x - canvas.getLocationOnScreen().x - (canvas.x + screenCoords.x) * zoomLevel / retinaScale) / zoomLevel * retinaScale);
        }

        private int getCN1Y(MouseEvent e) {
            if (canvas == null) {
                int out = e.getYOnScreen();
                if (out == 0) {
                    // For some reason the web browser would return 0 for screen coordinates
                    // but would still have correct values for getX() and getY() when 
                    // dealing with mouse wheel events.  In these cases we need to 
                    // get the screen coordinate from the component
                    // and add it to the relative coordinate.
                    out = e.getY();
                    Object source = e.getSource();
                    if (source instanceof java.awt.Component) {
                        Point pt = ((java.awt.Component)source).getLocationOnScreen();
                        out  += pt.y;
                    }
                }
                return out;
            }
            java.awt.Rectangle screenCoords = getScreenCoordinates();
            if (screenCoords == null) {
                screenCoords = new java.awt.Rectangle(0, 0, 0, 0);
            }
            int y = e.getYOnScreen();
            if (y == 0) {
                // For some reason the web browser would return 0 for screen coordinates
                // but would still have correct values for getX() and getY() when 
                // dealing with mouse wheel events.  In these cases we need to 
                // get the screen coordinate from the component
                // and add it to the relative coordinate.
                y = e.getY();
                Object source = e.getSource();
                if (source instanceof java.awt.Component) {
                    Point pt = ((java.awt.Component)source).getLocationOnScreen();
                    y += pt.y;
                }
            }
            return (int)((y - canvas.getLocationOnScreen().y - (canvas.y + screenCoords.y) * zoomLevel / retinaScale) / zoomLevel * retinaScale);
        }
        
        public CN1JFXPanel() {
            final CN1JFXPanel panel = this;
            
            /*
            panel.addMouseListener(new MouseListener() {
                
                

                @Override
                public void mouseClicked(MouseEvent e) {
                    sendToCn1(e);
                }

                @Override
                public void mousePressed(MouseEvent e) {
                    sendToCn1(e);
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    sendToCn1(e);
                }

                @Override
                public void mouseEntered(MouseEvent e) {
                    //SEBrowserComponent.this.instance.canvas.mouseE
                }

                @Override
                public void mouseExited(MouseEvent e) {
                    //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
                }
            });

            panel.addMouseMotionListener(new MouseMotionListener() {

                @Override
                public void mouseDragged(MouseEvent e) {
                    sendToCn1(e);
                }

                @Override
                public void mouseMoved(MouseEvent e) {
                    sendToCn1(e);
                }

            });

            panel.addMouseWheelListener(new MouseWheelListener() {

                @Override
                public void mouseWheelMoved(MouseWheelEvent e) {
                    sendToCn1(e);
                }

            });
            */
            
        }

        
    }
    
    protected BrowserWindowFactory createBrowserWindowFactory() {
        return new BrowserWindowFactory() {
            @Override
            public AbstractBrowserWindowSE createBrowserWindow(String startURL) {
                return new FXBrowserWindowSE(startURL);
            }

       };
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy