/*
 * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.sun.javafx.media;

import java.nio.ByteBuffer;
import java.util.Map;
import java.util.WeakHashMap;
import com.sun.glass.ui.Screen;
import com.sun.javafx.tk.RenderJob;
import com.sun.javafx.tk.Toolkit;
import com.sun.media.jfxmedia.control.VideoDataBuffer;
import com.sun.media.jfxmedia.control.VideoFormat;
import com.sun.prism.Graphics;
import com.sun.prism.GraphicsPipeline;
import com.sun.prism.MediaFrame;
import com.sun.prism.PixelFormat;
import com.sun.prism.ResourceFactoryListener;
import com.sun.prism.Texture;

Prism-specific subclass for handling media frames. The frames uploaded into per-Screen textures, updated on every frame.
/** * Prism-specific subclass for handling media frames. The frames uploaded * into per-Screen textures, updated on every frame. * */
public class PrismMediaFrameHandler implements ResourceFactoryListener { private final Map<Screen, TextureMapEntry> textures = new WeakHashMap<>(1); private static Map<Object, PrismMediaFrameHandler> handlers; public synchronized static PrismMediaFrameHandler getHandler(Object provider) { if (provider == null) { throw new IllegalArgumentException("provider must be non-null"); } if (handlers == null) { handlers = new WeakHashMap<>(1); } PrismMediaFrameHandler ret = handlers.get(provider); if (ret == null) { ret = new PrismMediaFrameHandler(provider); handlers.put(provider, ret); } return ret; } private boolean registeredWithFactory = false; private PrismMediaFrameHandler(Object provider) { } /* NOTE: The following methods will only ever happen on one thread, so thread * safety should not be a concern here. */
This should only ever be called during a render cycle. Any other time it will return null. Note that a returned texture should be unlocked when the caller no longer needs it.
Params:
  • g – the Graphics context about to be rendered into
Returns:the current media texture valid for rendering into g or null if called outside a render cycle
/** * This should only ever be called during a render cycle. Any other time it * will return null. Note that a returned texture should be unlocked when * the caller no longer needs it. * * @param g the Graphics context about to be rendered into * @return the current media texture valid for rendering into <code>g</code> * or null if called outside a render cycle */
public Texture getTexture(Graphics g, VideoDataBuffer currentFrame) { Screen screen = g.getAssociatedScreen(); TextureMapEntry tme = textures.get(screen); if (null == currentFrame) { // null frame, remove the existing texture if (textures.containsKey(screen)) { textures.remove(screen); } return null; } if (null == tme) { // we need to create a new texture for this graphics context tme = new TextureMapEntry(); textures.put(screen, tme); } if (tme.texture != null) { tme.texture.lock(); if (tme.texture.isSurfaceLost()) { tme.texture = null; } } // check if it needs to be updated if (null == tme.texture || tme.lastFrameTime != currentFrame.getTimestamp()) { updateTexture(g, currentFrame, tme); } return tme.texture; } private void updateTexture(Graphics g, VideoDataBuffer vdb, TextureMapEntry tme) { Screen screen = g.getAssociatedScreen(); // reset texture if the encoded size changes if (tme.texture != null && (tme.encodedWidth != vdb.getEncodedWidth() || tme.encodedHeight != vdb.getEncodedHeight())) { tme.texture.dispose(); tme.texture = null; } PrismFrameBuffer prismBuffer = new PrismFrameBuffer(vdb); if (tme.texture == null) { if (!registeredWithFactory) { // make sure we've registered with the resource factory so we know // when to purge old textures GraphicsPipeline.getDefaultResourceFactory().addFactoryListener(this); registeredWithFactory = true; } tme.texture = GraphicsPipeline.getPipeline(). getResourceFactory(screen). createTexture(prismBuffer); tme.encodedWidth = vdb.getEncodedWidth(); tme.encodedHeight = vdb.getEncodedHeight(); } // upload frame data, check for null in case createTexture fails if (tme.texture != null) { tme.texture.update(prismBuffer, false); } tme.lastFrameTime = vdb.getTimestamp(); } private void releaseData() { for (TextureMapEntry tme : textures.values()) { if (tme != null && tme.texture != null) { tme.texture.dispose(); } } textures.clear(); } private final RenderJob releaseRenderJob = new RenderJob(() -> { releaseData(); });
Call this when you no longer need to render movie frames, for example when playback stops.
/** * Call this when you no longer need to render movie frames, for example * when playback stops. */
public void releaseTextures() { Toolkit tk = Toolkit.getToolkit(); tk.addRenderJob(releaseRenderJob); } public void factoryReset() { releaseData(); } public void factoryReleased() { releaseData(); }
Bridge class to avoid having to import JFXMedia into a bunch of prism code.
/** * Bridge class to avoid having to import JFXMedia into a bunch of prism * code. */
private class PrismFrameBuffer implements MediaFrame { private final PixelFormat videoFormat; private final VideoDataBuffer master; public PrismFrameBuffer(VideoDataBuffer sourceBuffer) { if (null == sourceBuffer) { throw new NullPointerException(); } master = sourceBuffer; switch (master.getFormat()) { case BGRA_PRE: videoFormat = PixelFormat.INT_ARGB_PRE; break; case YCbCr_420p: videoFormat = PixelFormat.MULTI_YCbCr_420; break; case YCbCr_422: videoFormat = PixelFormat.BYTE_APPLE_422; break; // ARGB isn't supported in prism, there's no corresponding PixelFormat case ARGB: default: throw new IllegalArgumentException("Unsupported video format "+master.getFormat()); } } @Override public ByteBuffer getBufferForPlane(int plane) { return master.getBufferForPlane(plane); } @Override public void holdFrame() { master.holdFrame(); } @Override public void releaseFrame() { master.releaseFrame(); } @Override public PixelFormat getPixelFormat() { return videoFormat; } @Override public int getWidth() { return master.getWidth(); } @Override public int getHeight() { return master.getHeight(); } @Override public int getEncodedWidth() { return master.getEncodedWidth(); } @Override public int getEncodedHeight() { return master.getEncodedHeight(); } @Override public int planeCount() { return master.getPlaneCount(); } @Override public int[] planeStrides() { return master.getPlaneStrides(); } @Override public int strideForPlane(int planeIndex) { return master.getStrideForPlane(planeIndex); } @Override public MediaFrame convertToFormat(PixelFormat fmt) { if (fmt == getPixelFormat()) { return this; } // This method only supports conversion to INT_ARGB_PRE currently if (fmt != PixelFormat.INT_ARGB_PRE) { return null; } VideoDataBuffer newVDB = master.convertToFormat(VideoFormat.BGRA_PRE); if (null == newVDB) { return null; } return new PrismFrameBuffer(newVDB); } } private static class TextureMapEntry { public double lastFrameTime = -1; // used to determine if we need to update public Texture texture; public int encodedWidth; public int encodedHeight; } }