Related
How can I change the damage of the enchant sharpness in minecraft 1.12.2? I have tried this method but I don't understand how to go on
private RubySharpnessFixer plugin;
public EnchantmentWeaponDamageCustom(RubySharpnessFixer plugin, Rarity rarity, int i, EnumItemSlot... enumItemSlots) {
super(rarity, i, enumItemSlots);
this.plugin = plugin;
}
public float a(int i, EnumMonsterType enummonstertype) {
return (this.a == 0) ? (float)(Math.max(1, i) * plugin.getConfig().getDouble("damage-per-level")) : ((this.a == 1 && enummonstertype == EnumMonsterType.UNDEAD) ? (i * 2.5F) : ((this.a == 2 && enummonstertype == EnumMonsterType.ARTHROPOD) ? (i * 2.5F) : 0.0F));
}
public boolean hook() {
EnchantmentWeaponDamageCustom enchantmentWeaponDamageCustom = new EnchantmentWeaponDamageCustom(plugin, Rarity.COMMON, 16, EnumItemSlot.MAINHAND);
}
} ```
There is multiple ways :
1. In-game: Command
attribute #s minecraft:generic.attack_damage modifier add 0-0-0-0-0 attribute_name .25 add
(replace the 0-0-0-0-0 by a valid UUID)
There is a reddit post that explain it.
But I think it's not what you are looking for.
2. Plugin: NMS & reflection
Firstly, you have to change the enchant currently registered. Such as it's not authorized by default, we have to use reflection.
// import
import java.lang.reflect.Field;
import org.bukkit.craftbukkit.v1_12_R1.enchantments.CraftEnchantment;
import org.bukkit.enchantments.Enchantment;
// now the code :
try {
Enchantment enchantToChange = Enchantment.DAMAGE_ALL; // the enchant that we want to change
// SharpnessModifier is a class that is showed after
SharpnessModifier sharpness = new SharpnessModifier(0);
net.minecraft.server.v1_12_R1.Enchantment.enchantments.a(16, new MinecraftKey("sharpness"), sharpness); // add enchants to NMS class
CraftEnchantment newEnchant = new CraftEnchantment(sharpness);
Field byNameField = Enchantment.class.getDeclaredField("byName"); // enchant by name
byNameField.setAccessible(true);
Map<String, Enchantment> byName = (Map<String, Enchantment>) byNameField.get(null);
byName.put(enchantToChange.getName(), newEnchant);
Field byIdField = Enchantment.class.getDeclaredField("byId"); // enchant by ID
byIdField.setAccessible(true);
Map<Integer, Enchantment> byId = (Map<Integer, Enchantment>) byIdField.get(null);
byId.put(enchantToChange.getId(), newEnchant);
} catch (Exception e) {
e.printStackTrace();
}
Then, you have to create a new class that will manage all rules that you want to define for this enchant.
import net.minecraft.server.v1_12_R1.EnchantmentWeaponDamage;
import net.minecraft.server.v1_12_R1.EnumMonsterType;
import net.minecraft.server.v1_12_R1.ItemStack;
public class SharpnessModifier extends EnchantmentWeaponDamage {
public SharpnessModifier(int i){
super(Rarity.COMMON, i);
}
#Override
public int getMaxLevel() {
return 200;
}
#Override
public float a(int i, EnumMonsterType enumMonsterType){
// it's here that you will have what you want. You can calculate the damage
// default calculation :
/*
if (a == 0)
return (float) i * 5.0F;
if (a == 1 && enumMonsterType == EnumMonsterType.UNDEAD)
return (float) i * 2.5F;
if (a == 2 && enumMonsterType == EnumMonsterType.ARTHROPOD)
return (float) i * 2.5F;
else
return 0.0F;
*/
return Float.MAX_VALUE; // max damage, just to try. I just OS a wither
}
#Override
public boolean canEnchant(ItemStack item) {
return true; // allow for all item
}
}
3. Plugin: Damage event
This can also be done with packet.
You have to intercept damage event, and change damage value if player have sharpness.
#EventHandler
public void a(EntityDamageByEntityEvent e) {
if(e.getDamager() instanceof Player) {
Player p = (Player) e.getDamager();
ItemStack item = p.getItemInHand();
if(item.containsEnchantment(Enchantment.DAMAGE_ALL)) {
// manage damage with "e.setDamage(damage);"
}
}
}
It's not a very good solution because other things can interact with damage: critical, time (1.9+ pvp) ...
Here is an example of what I'm working with...
Player p = (Player) sender;
ArrayList<Player> nofalldmg = new ArrayList<Player>();
if (p.hasPermission("custome.fly")) {
if (p.isFlying()) {
p.setAllowFlight(false);
p.setFlying(false);
nofalldmg.add(p);
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
public void run() {
nofalldmg.remove(p);
}
}, 60);
So as you can see I have two lines of code 1 "nofalldmg.add(p);" that adds the player to an array list, and 2 "nofalldmg.remove(p);" that removes the player from the array list
When they are added to the array list their fall damage is canceled, what I want to know is once they are removed from that array list how do I re enable their fall damage?
This is the full class that can help you (without import, with comment to explain how it works) :
private final ArrayList<Player> nofalldmg = new ArrayList<Player>(); // declare list
#EventHandler
public void onDamageDisabler(EntityDamageEvent e) { // method to disable fall damage
if (e.getCause() == EntityDamageEvent.DamageCause.FALL) { // only if it's fall
if(e.getEntity() instanceof Player && nofalldmg.contains((Player) e.getEntity()) { // only if it's in list
e.setCancelled(true); // cancel
}
}
}
public void yourMethod(CommandSender sender) {
Player p = (Player) sender;
if (p.hasPermission("custome.fly")) { // your conditions
if (p.isFlying()) {
p.setAllowFlight(false);
p.setFlying(false);
nofalldmg.add(p); // here we add it to the list
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> {
nofalldmg.remove(p);
// here you can resetting everything that you want
}, 60); // no we remove it, so can take damage
}
}
}
Would someone be able to help me please.
I am trying to complete exercise 13 from chapter 8 of the Art and Science of Java and have became stuck.
The program displays a set of towers (GRects), and then the user needs to click on any tower to have it send a signal to 'light (change the color)' the next tower in the chain. In the background with system.out I can see the signals being sent, however the event dispatcher waits (I read on another site) for this process to finish before updating the colors. Each object also has a time thread.sleep(300). My question is: Can someone please teach me how to write a new thread or show me what I am doing wrong and how I should go about fixing it. I have looked at Oracle docs on threads and can't understand what everything is, I am also new to Java and and doing self-study. While getting the exercise working is needed, I really want to learn how to make a program like this so that in the future I don't get stuck again. Any help from the online community would be greatly appreciated. Thank you in advance.
The code:
/**
* Class name: SignalTower
* -----------------------
* This class defines a signal tower object that passes a message to the
* next tower in a line.
*
* Programmer:
* Date: 2015/12/25
*/
package com.chapter8;
import java.awt.Color;
import acm.graphics.GCompound;
import acm.graphics.GLabel;
import acm.graphics.GRect;
/* Class: SignalTower */
/**
* This class defines a signal tower object that passes a message
* to the next tower in a line.
*/
public class SignalTower extends GCompound {
GRect tower = new GRect(TOWER_WIDTH,TOWER_HEIGHT);
/* Constructor: SignalTower(name, link) */
/**
* Constructs a new signal tower with the following parameters:
*
* #param name The name of the tower
* #param link A link to the next tower, or null if none exists
*/
public SignalTower(String name, SignalTower link) {
towerName = name;
nextTower = link;
buildTower();
}
/* Method: signal() */
/**
* This method represents sending a signal to this tower. The effect
* is to light the signal fire here and to send an additional signal
* message to the next tower in the chain, if any.
*/
public void signal() {
lightCurrentTower();
if (nextTower != null) nextTower.signal();
}
/* Method: lightCurrentTower() */
/**
* This method lights the signal fire for this tower. This version
* supplies a temporary implementation (typically called a "stub")
* that simply prints the name of the tower to the standard output
* channel. If you wanted to redesign this class to be part of a
* graphical application, for example, you could override this
* method to draw an indication of the signal fire on the display.
*/
public void lightCurrentTower() {
try {
this.setTowerColor();
Thread.sleep(300);
} catch (InterruptedException e) {
// Handle here
}
System.out.println("Lighting " + towerName);
}
public void buildTower(){
tower.setFilled(false);
add(tower);
GLabel label = new GLabel(towerName);
label.setLocation(getTowerWidth()/2 - label.getWidth()/2, this.getTowerLabelY());
add(label);
}
public void setTowerColor(){
tower.setColor(Color.RED);
tower.setFillColor(Color.RED);
tower.setFilled(true);
}
public int getTowerWidth(){
return TOWER_WIDTH;
}
public int getTowerSpace(){
return TOWER_SPACE;
}
public int getTowerLabelY(){
return TOWER_LABEL_Y;
}
/* Private instance variables */
private String towerName; /* The name of this tower */
private SignalTower nextTower; /* A link to the next tower */
private static final int TOWER_WIDTH = 30;
private static final int TOWER_HEIGHT = 180;
private static final int TOWER_SPACE = 100;
private static final int TOWER_LABEL_Y = 200;
}
/**
* File name: BeaconsOfGondor.java
* -------------------------------
* Message passing in linked structures: The beacons of Gondor:
*
* For answer Gandalf cried aloud to his horse. “On, Shadowfax! We must hasten.
* Time is short. See! The beacons of Gondor are alight, calling for aid. War
* is kindled. See, there is the fire on Amon Dîn, and flame on Eilenach; and
* there they go speeding west: Nardol, Erelas, Min-Rimmon, Calenhad, and the
* Halifirien on the borders of Rohan.”
* —J. R. R. Tolkien, The Return of the King, 1955
*
* This program creates a graphical representation of linked structures using
* this example.
*
* Programmer: Peter Lock
* Date: 2016/1/5
*/
package com.chapter8;
import java.awt.Point;
import java.awt.event.MouseEvent;
import acm.program.GraphicsProgram;
public class BeaconsOfGondor extends GraphicsProgram implements Runnable{
private Object gobj;
public String flag = "";
SignalTower[] towers = new SignalTower[10];
SignalTower rohan = new SignalTower("Rohan", null);
SignalTower halifirien = new SignalTower("Halifirien", rohan);
SignalTower calenhad = new SignalTower("Calenhad", halifirien);
SignalTower minRimmon = new SignalTower("Min-Rimmon", calenhad);
SignalTower erelas = new SignalTower("Erelas", minRimmon);
SignalTower nardol = new SignalTower("Nardol", erelas);
SignalTower eilenach = new SignalTower("Eilenach", nardol);
SignalTower amonDin = new SignalTower("Amon Din", eilenach);
SignalTower minasTirith = new SignalTower("Minas Tirith", amonDin);
public void run(){
createTowers();
addMouseListeners();
// minasTirith.signal();
}
private void createTowers() {
add(minasTirith);
minasTirith.setLocation(minasTirith.getTowerSpace(), 30);
add(amonDin);
amonDin.setLocation(amonDin.getTowerSpace()*2, 30);
add(eilenach);
eilenach.setLocation(eilenach.getTowerSpace()*3, 30);
add(nardol);
nardol.setLocation(nardol.getTowerSpace()*4, 30);
add(erelas);
erelas.setLocation(erelas.getTowerSpace()*5, 30);
add(minRimmon);
minRimmon.setLocation(minRimmon.getTowerSpace()*6, 30);
add(calenhad);
calenhad.setLocation(calenhad.getTowerSpace()*7, 30);
add(halifirien);
halifirien.setLocation(halifirien.getTowerSpace()*8, 30);
add(rohan);
rohan.setLocation(rohan.getTowerSpace()*9, 30);
}
public void mousePressed(MouseEvent e) {
gobj = getElementAt(e.getX(), e.getY());
if(gobj != null){
Point mp = e.getPoint();
if(minasTirith.contains(mp.getX(), mp.getY())) minasTirith.signal();
if(amonDin.contains(mp.getX(), mp.getY())) amonDin.signal();
if(eilenach.contains(mp.getX(), mp.getY())) eilenach.signal();
if(nardol.contains(mp.getX(), mp.getY())) nardol.signal();
if(erelas.contains(mp.getX(), mp.getY())) erelas.signal();
if(minRimmon.contains(mp.getX(), mp.getY())) minRimmon.signal();
if(calenhad.contains(mp.getX(), mp.getY())) calenhad.signal();
if(halifirien.contains(mp.getX(), mp.getY())) halifirien.signal();
if(rohan.contains(mp.getX(), mp.getY())) rohan.signal();
}
}
}
I'm using JavaFX and Scene Builder and I have a form with textfields. Three of these textfields are parsed from strings to doubles.
I want them to be school marks so they should only be allowed to be between 1.0 and 6.0. The user should not be allowed to write something like "2.34.4" but something like "5.5" or "2.9" would be ok.
Validation for the parsed fields:
public void validate(KeyEvent event) {
String content = event.getCharacter();
if ("123456.".contains(content)) {
// No numbers smaller than 1.0 or bigger than 6.0 - How?
} else {
event.consume();
}
}
How can I test if the user inputs a correct value?
I already searched on Stackoverflow and on Google but I didn't find a satisfying solution.
textField.focusedProperty().addListener((arg0, oldValue, newValue) -> {
if (!newValue) { //when focus lost
if(!textField.getText().matches("[1-5]\\.[0-9]|6\\.0")){
//when it not matches the pattern (1.0 - 6.0)
//set the textField empty
textField.setText("");
}
}
});
you could also change the pattern to [1-5](\.[0-9]){0,1}|6(.0){0,1} then 1,2,3,4,5,6would also be ok (not only 1.0,2.0,...)
update
Here is a small test application with the values 1(.00) to 6(.00) allowed:
public class JavaFxSample extends Application {
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Enter number and hit the button");
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
Label label1To6 = new Label("1.0-6.0:");
grid.add(label1To6, 0, 1);
TextField textField1To6 = new TextField();
textField1To6.focusedProperty().addListener((arg0, oldValue, newValue) -> {
if (!newValue) { // when focus lost
if (!textField1To6.getText().matches("[1-5](\\.[0-9]{1,2}){0,1}|6(\\.0{1,2}){0,1}")) {
// when it not matches the pattern (1.0 - 6.0)
// set the textField empty
textField1To6.setText("");
}
}
});
grid.add(textField1To6, 1, 1);
grid.add(new Button("Hit me!"), 2, 1);
Scene scene = new Scene(grid, 300, 275);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I would not advise you to use KeyEvent for that.
You should use a more classical way such as validated the user input when the user finish to fill the text field or click on a save button.
/**
* Called this when the user clicks on the save button or finish to fill the text field.
*/
private void handleSave() {
// If the inputs are valid we save the data
if(isInputValid()){
note=(DOUBLE.parseDouble(textField.getText()));
}else // do something such as notify the user and empty the field
}
/**
* Validates the user input in the text fields.
*
* #return true if the input is valid
*/
private boolean isInputValid() {
Boolean b= false;
if (!(textField.getText() == null || textFiled.getText().length() == 0)) {
try {
// Do all the validation you need here such as
Double d = Double.parseInt(textFiled.getText());
if ( 1.0<d<6.0){
b=true;
}
} catch (NumberFormatException e) {
}
return b;
}
You can prevent illegal input using TextFormatter:
final Pattern pattern = Pattern.compile("(6\\.0)|([1-5]\\.[0-9])");
textField.setTextFormatter(new TextFormatter<>(new DoubleStringConverter(), 0.0, change -> {
final Matcher matcher = pattern.matcher(change.getControlNewText());
return (matcher.matches() || matcher.hitEnd()) ? change : null;
}));
In case you can use a third party library:
Similar question has been aswered here: Form validator message
.
For your case, you would choose a RegexValidator to check the textfield input, and pass the regex that you arrived to from previous answers:
JFXTextField validationField = new JFXTextField();
validationField.setPromptText("decimal between 1.0 and 6.0");
RegexValidator validator = new RegexValidator();
validator.setRegexPattern("[1-5](\\.[0-9]{1,2}){0,1}|6(\\.0{1,2}){0,1}");
validator.setMessage("Please enter proper value");
validationField.getValidators().add(validator);
validationField.focusedProperty().addListener((observable, oldValue, newValue) -> {
if(!newValue)
validationField.validate();
});
You can make a custom TextField that does input validation if you want.
import java.awt.Toolkit;
import javafx.event.EventHandler;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.input.KeyEvent;
/**
* A text field that limits the user to certain number of characters and
* prevents the user from typing certain characters
*
*
*/
public class CustomTextField extends TextField
{
/**
* The maximum number of characters this text field will allow
* */
private int maxNumOfCharacters;
/**
* A regular expression of characters that this text field does not allow
* */
private String unallowedCharactersRegEx;
/*
* If no max number of characters is specified the default value is set
* */
private static final int DEFAULT_MAX_NUM_OF_CHARACTERS = 1000;
public CustomTextField()
{
maxNumOfCharacters = DEFAULT_MAX_NUM_OF_CHARACTERS;
this.setOnKeyTyped(new EventHandler<KeyEvent>() {
public void handle(KeyEvent event)
{
// get the typed character
String characterString = event.getCharacter();
char c = characterString.charAt(0);
// if it is a control character or it is undefined, ignore it
if (Character.isISOControl(c) || characterString.contentEquals(KeyEvent.CHAR_UNDEFINED))
return;
// get the text field/area that triggered this key event and its text
TextInputControl source = (TextInputControl) event.getSource();
String text = source.getText();
// If the text exceeds its max length or if a character that matches
// notAllowedCharactersRegEx is typed
if (text.length() > maxNumOfCharacters
|| (unallowedCharactersRegEx != null && characterString.matches(unallowedCharactersRegEx)))
{
// remove the last character
source.deletePreviousChar();
// make a beep sound effect
Toolkit.getDefaultToolkit().beep();
}
}
});
}
public int getMaxNumOfCharacters()
{
return maxNumOfCharacters;
}
public void setMaxNumOfCharacters(int maxNumOfCharacters)
{
this.maxNumOfCharacters = maxNumOfCharacters;
}
public String getUnallowedCharactersRegEx()
{
return unallowedCharactersRegEx;
}
public void setUnallowedCharactersRegEx(String notAllowedRegEx)
{
this.unallowedCharactersRegEx = notAllowedRegEx;
}
}
I'm using google/grafika's examples to decode, transform and encode back to file a video clip. The transformation is downscaling and translating, it is done via shader stored in Texture2dProgram. My main activity is based on CameraCaptureActivity. The catch is I'm placing two videos on single texture at the same time, side by side. I would like to delay one of them for a given amount of frames. Also note that I don't need display preview while encoding.
My best idea so far was to change timestamps while advancing through frames. In TextureMovieEncoder, I'm sending information about frames, including timestamp in which they has to be placed in result video. It takes place in frameAvailiable(), where I'm sending information about two frames at once (left and right). The idea was to increase timestamp of one of them. The problem is that result video is distorted, so I don't know if my approach is feasible. TextureMovieEncoder is posted below.
package com.android.grafika;
import android.graphics.SurfaceTexture;
import android.opengl.EGLContext;
import android.opengl.GLES20;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import com.android.grafika.gles.EglCore;
import com.android.grafika.gles.FullFrameRect;
import com.android.grafika.gles.Texture2dProgram;
import com.android.grafika.gles.WindowSurface;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
/**
* Encode a movie from frames rendered from an external texture image.
* <p>
* The object wraps an encoder running on a dedicated thread. The various control messages
* may be sent from arbitrary threads (typically the app UI thread). The encoder thread
* manages both sides of the encoder (feeding and draining); the only external input is
* the GL texture.
* <p>
* The design is complicated slightly by the need to create an EGL context that shares state
* with a view that gets restarted if (say) the device orientation changes. When the view
* in question is a GLSurfaceView, we don't have full control over the EGL context creation
* on that side, so we have to bend a bit backwards here.
* <p>
* To use:
* <ul>
* <li>create TextureMovieEncoder object
* <li>create an EncoderConfig
* <li>call TextureMovieEncoder#startRecording() with the config
* <li>call TextureMovieEncoder#setTextureId() with the texture object that receives frames
* <li>for each frame, after latching it with SurfaceTexture#updateTexImage(),
* call TextureMovieEncoder#frameAvailable().
* </ul>
*
* TODOO: tweak the API (esp. textureId) so it's less awkward for simple use cases.
*/
public class TextureMovieEncoder implements Runnable {
private static final String TAG = MainActivity.TAG;
private static final boolean VERBOSE = false;
private static final long timestampCorrection = 1000000000;
private long timestampCorected;
private static final int MSG_START_RECORDING = 0;
private static final int MSG_STOP_RECORDING = 1;
private static final int MSG_FRAME_AVAILABLE = 2;
private static final int MSG_SET_TEXTURE_ID = 3;
private static final int MSG_UPDATE_SHARED_CONTEXT = 4;
private static final int MSG_QUIT = 5;
private boolean measure_started = false;
private long startTime = -1;
private int cycle = 0;
private long handleFrameTime = 0;
private long last_timestamp = -1;
private float [] transform;
private long last_orig_timestamp = -1;
public long getFrame() {
return frame;
}
private long frame = 0;
private long average_diff = 0;
private long step = 40000000;
private long actTimestamp = 0;
private boolean shouldStop = false;
public void setmSpeedCallback(SpeedControlCallback mSpeedCallback) {
this.mSpeedCallback = mSpeedCallback;
}
private SpeedControlCallback mSpeedCallback;
// ----- accessed exclusively by encoder thread -----
private WindowSurface mInputWindowSurface;
private EglCore mEglCore;
private FullFrameRect mFullScreen;
private int mTextureId;
private VideoEncoderCore mVideoEncoder;
// ----- accessed by multiple threads -----
private volatile EncoderHandler mHandler;
private Object mReadyFence = new Object(); // guards ready/running
private boolean mReady;
private boolean mRunning;
/**
* Encoder configuration.
* <p>
* Object is immutable, which means we can safely pass it between threads without
* explicit synchronization (and don't need to worry about it getting tweaked out from
* under us).
* <p>
* TODO: make frame rate and iframe interval configurable? Maybe use builder pattern
* with reasonable defaults for those and bit rate.
*/
public static class EncoderConfig {
final File mOutputFile;
final int mWidth;
final int mHeight;
final int mBitRate;
final EGLContext mEglContext;
public EncoderConfig(File outputFile, int width, int height, int bitRate,
EGLContext sharedEglContext) {
mOutputFile = outputFile;
mWidth = width;
mHeight = height;
mBitRate = bitRate;
mEglContext = sharedEglContext;
}
#Override
public String toString() {
return "EncoderConfig: " + mWidth + "x" + mHeight + " #" + mBitRate +
" to '" + mOutputFile.toString() + "' ctxt=" + mEglContext;
}
}
/**
* Tells the video recorder to start recording. (Call from non-encoder thread.)
* <p>
* Creates a new thread, which will create an encoder using the provided configuration.
* <p>
* Returns after the recorder thread has started and is ready to accept Messages. The
* encoder may not yet be fully configured.
*/
public void startRecording(EncoderConfig config) {
Log.d(TAG, "Encoder: startRecording()");
synchronized (mReadyFence) {
if (mRunning) {
Log.w(TAG, "Encoder thread already running");
return;
}
mRunning = true;
new Thread(this, "TextureMovieEncoder").start();
while (!mReady) {
try {
mReadyFence.wait();
} catch (InterruptedException ie) {
// ignore
}
}
}
mHandler.sendMessage(mHandler.obtainMessage(MSG_START_RECORDING, config));
}
/**
* Tells the video recorder to stop recording. (Call from non-encoder thread.)
* <p>
* Returns immediately; the encoder/muxer may not yet be finished creating the movie.
* <p>
* TODO: have the encoder thread invoke a callback on the UI thread just before it shuts down
* so we can provide reasonable status UI (and let the caller know that movie encoding
* has completed).
*/
public void stopRecording() {
//mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_RECORDING));
//mHandler.sendMessage(mHandler.obtainMessage(MSG_QUIT));
// We don't know when these will actually finish (or even start). We don't want to
// delay the UI thread though, so we return immediately.
shouldStop = true;
Log.d(TAG, "Shout down flag set up.");
}
/**
* Returns true if recording has been started.
*/
public boolean isRecording() {
synchronized (mReadyFence) {
return mRunning;
}
}
/**
* Tells the video recorder to refresh its EGL surface. (Call from non-encoder thread.)
*/
public void updateSharedContext(EGLContext sharedContext) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_SHARED_CONTEXT, sharedContext));
}
/**
* Tells the video recorder that a new frame is available. (Call from non-encoder thread.)
* <p>
* This function sends a message and returns immediately. This isn't sufficient -- we
* don't want the caller to latch a new frame until we're done with this one -- but we
* can get away with it so long as the input frame rate is reasonable and the encoder
* thread doesn't stall.
* <p>
* TODO: either block here until the texture has been rendered onto the encoder surface,
* or have a separate "block if still busy" method that the caller can execute immediately
* before it calls updateTexImage(). The latter is preferred because we don't want to
* stall the caller while this thread does work.
*/
public void frameAvailable(SurfaceTexture st) {
synchronized (mReadyFence) {
if (!mReady) {
return;
}
}
transform = new float[16]; // TODOO - avoid alloc every frame
st.getTransformMatrix(transform);
long timestamp = st.getTimestamp();
// if first frame
if (last_timestamp < 0) {
if (!measure_started) {
startTime = System.currentTimeMillis();
measure_started = true;
}
last_timestamp = timestamp;
last_orig_timestamp = timestamp;
}
else {
// HARDCODED FRAME NUMBER :(
// if playback finished or frame number reached
if ((frame == 200) || shouldStop) {
if (measure_started) {
long stopTime = System.currentTimeMillis();
long elapsedTime = stopTime - startTime;
Log.d(TAG, "Rendering time: " + (double)elapsedTime * 0.001 + "[s]");
Log.d(TAG, "HandlingFrame time: " + (double)(stopTime - handleFrameTime) * 0.001 + "[s]");
measure_started = false;
}
mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_RECORDING));
mHandler.sendMessage(mHandler.obtainMessage(MSG_QUIT));
return;
}
else if (timestamp == 0) {
// Seeing this after device is toggled off/on with power button. The
// first frame back has a zero timestamp.
//
// MPEG4Writer thinks this is cause to abort() in native code, so it's very
// important that we just ignore the frame.
Log.w(TAG, "HEY: got SurfaceTexture with timestamp of zero");
return;
}
// this is workaround for duplicated timestamp
// might cause troubles with some videos
else if ((timestamp == last_orig_timestamp)) {
return;
}
else {
frame++;
mHandler.sendMessage(mHandler.obtainMessage(MSG_FRAME_AVAILABLE,
(int) (actTimestamp >> 32), (int) actTimestamp, transform));
timestampCorected = actTimestamp + timestampCorrection;
mHandler.sendMessage(mHandler.obtainMessage(MSG_FRAME_AVAILABLE,
(int) (timestampCorected >> 32), (int) timestampCorected, transform));
actTimestamp += step;
}
last_orig_timestamp = timestamp;
}
}
/**
* Calculates 'average' diffrence between frames.
* Result is based on first 50 frames.
* Shuld be called in frameAvailiable.
*
* #param timestamp actual frame timestamp
*/
private void calcAndShowAverageDiff(long timestamp) {
if ((frame < 50) && (frame > 0)) {
average_diff += timestamp - last_timestamp;
last_timestamp = timestamp;
}
if (frame == 50) {
average_diff /= frame;
Log.d(TAG, "Average timestamp difference: " + Long.toString(average_diff));
}
}
/**
* Tells the video recorder what texture name to use. This is the external texture that
* we're receiving camera previews in. (Call from non-encoder thread.)
* <p>
* TODOO: do something less clumsy
*/
public void setTextureId(int id) {
synchronized (mReadyFence) {
if (!mReady) {
return;
}
}
mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_TEXTURE_ID, id, 0, null));
}
/**
* Encoder thread entry point. Establishes Looper/Handler and waits for messages.
* <p>
* #see java.lang.Thread#run()
*/
#Override
public void run() {
// Establish a Looper for this thread, and define a Handler for it.
Looper.prepare();
synchronized (mReadyFence) {
mHandler = new EncoderHandler(this);
mReady = true;
mReadyFence.notify();
}
Looper.loop();
Log.d(TAG, "Encoder thread exiting");
synchronized (mReadyFence) {
mReady = mRunning = false;
mHandler = null;
}
}
/**
* Handles encoder state change requests. The handler is created on the encoder thread.
*/
private static class EncoderHandler extends Handler {
private WeakReference<TextureMovieEncoder> mWeakEncoder;
public EncoderHandler(TextureMovieEncoder encoder) {
mWeakEncoder = new WeakReference<TextureMovieEncoder>(encoder);
}
#Override // runs on encoder thread
public void handleMessage(Message inputMessage) {
int what = inputMessage.what;
Object obj = inputMessage.obj;
TextureMovieEncoder encoder = mWeakEncoder.get();
if (encoder == null) {
Log.w(TAG, "EncoderHandler.handleMessage: encoder is null");
return;
}
switch (what) {
case MSG_START_RECORDING:
encoder.handleStartRecording((EncoderConfig) obj);
break;
case MSG_STOP_RECORDING:
encoder.handleStopRecording();
break;
case MSG_FRAME_AVAILABLE:
long timestamp = (((long) inputMessage.arg1) << 32) |
(((long) inputMessage.arg2) & 0xffffffffL);
encoder.handleFrameAvailable((float[]) obj, timestamp);
break;
case MSG_SET_TEXTURE_ID:
encoder.handleSetTexture(inputMessage.arg1);
break;
case MSG_UPDATE_SHARED_CONTEXT:
encoder.handleUpdateSharedContext((EGLContext) inputMessage.obj);
break;
case MSG_QUIT:
Looper.myLooper().quit();
break;
default:
throw new RuntimeException("Unhandled msg what=" + what);
}
}
}
/**
* Starts recording.
*/
private void handleStartRecording(EncoderConfig config) {
Log.d(TAG, "handleStartRecording " + config);
prepareEncoder(config.mEglContext, config.mWidth, config.mHeight, config.mBitRate,
config.mOutputFile);
}
/**
* Handles notification of an available frame.
* <p>
* The texture is rendered onto the encoder's input surface, along with a moving
* box (just because we can).
* <p>
* #param transform The texture transform, from SurfaceTexture.
* #param timestampNanos The frame's timestamp, from SurfaceTexture.
*/
private void handleFrameAvailable(float[] transform, long timestampNanos) {
if (VERBOSE) Log.d(TAG, "handleFrameAvailable tr=" + transform);
if (cycle == 1) {
mVideoEncoder.drainEncoder(false);
mFullScreen.drawFrame(mTextureId, transform, 1.0f);
}
else {
mFullScreen.drawFrame(mTextureId, transform, -1.0f);
}
mInputWindowSurface.setPresentationTime(timestampNanos);
mInputWindowSurface.swapBuffers();
if (cycle == 1) {
mSpeedCallback.setCanRelease(true);
cycle = 0;
} else
cycle++;
}
/**
* Handles a request to stop encoding.
*/
private void handleStopRecording() {
Log.d(TAG, "handleStopRecording");
mVideoEncoder.drainEncoder(true);
releaseEncoder();
}
/**
* Sets the texture name that SurfaceTexture will use when frames are received.
*/
private void handleSetTexture(int id) {
//Log.d(TAG, "handleSetTexture " + id);
mTextureId = id;
}
/**
* Tears down the EGL surface and context we've been using to feed the MediaCodec input
* surface, and replaces it with a new one that shares with the new context.
* <p>
* This is useful if the old context we were sharing with went away (maybe a GLSurfaceView
* that got torn down) and we need to hook up with the new one.
*/
private void handleUpdateSharedContext(EGLContext newSharedContext) {
Log.d(TAG, "handleUpdatedSharedContext " + newSharedContext);
// Release the EGLSurface and EGLContext.
mInputWindowSurface.releaseEglSurface();
mFullScreen.release(false);
mEglCore.release();
// Create a new EGLContext and recreate the window surface.
mEglCore = new EglCore(newSharedContext, EglCore.FLAG_RECORDABLE);
mInputWindowSurface.recreate(mEglCore);
mInputWindowSurface.makeCurrent();
// Create new programs and such for the new context.
mFullScreen = new FullFrameRect(
new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_SBS));
}
private void prepareEncoder(EGLContext sharedContext, int width, int height, int bitRate,
File outputFile) {
try {
mVideoEncoder = new VideoEncoderCore(width, height, bitRate, outputFile);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
mEglCore = new EglCore(sharedContext, EglCore.FLAG_RECORDABLE);
mInputWindowSurface = new WindowSurface(mEglCore, mVideoEncoder.getInputSurface(), true);
mInputWindowSurface.makeCurrent();
mFullScreen = new FullFrameRect(
new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_SBS));
}
private void releaseEncoder() {
mVideoEncoder.release();
if (mInputWindowSurface != null) {
mInputWindowSurface.release();
mInputWindowSurface = null;
}
if (mFullScreen != null) {
mFullScreen.release(false);
mFullScreen = null;
}
if (mEglCore != null) {
mEglCore.release();
mEglCore = null;
}
}
/**
* Draws a box, with position offset.
*/
private void drawBox(int posn) {
final int width = mInputWindowSurface.getWidth();
int xpos = (posn * 4) % (width - 50);
GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
GLES20.glScissor(xpos, 0, 100, 100);
GLES20.glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
}
}
Is my idea viable? Or is there better/correct way to delay one of the videos?
It appears that my first idea with switching timestamps was invalid.
Following Fadden's suggestion, I've succesfuly created delay by using two decoders. I've modified code of grafika's MoviePlayer so it contains two pairs of extractor-decoder. Decoders has separate output textures. Extracting loops are running in separate threads. I thought that this approach will cause some heavy drop in performance, but it appears that it didn't happen, performance is still acceptable for my needs.