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.
Related
I am working on an app which uploads a large amount of data. I want to determine the transfer rate of the upload, to show in a notification.
One post suggests using the WifiInfo which will not work for mobile data.
Another post suggests getting the network type to estimate the speed.
I'm not satisfied with the answers in these posts, so I am asking again.
I've seen apps which display the upload transfer rate, as well as some custom ROMs like Resurrection Remix.
How can I determine the transfer rate of these uploads?
It is feasible to obtain the transferred traffic amount using android.net.TrafficStats. Here is an implementation of this idea which measures the up-stream and down-stream transfer rate. You can measure the rate of mobile network by passing TrafficSpeedMeasurer.TrafficType.MOBILE to the TrafficSpeedMeasurer constructor, otherwise using TrafficSpeedMeasurer.TrafficType.ALL will result in measuring general traffic (WiFi/Mobile). Also by setting SHOW_SPEED_IN_BITS = true in MainActivity you can change the unit of speed measuring to bits per second.
MainActivity.java
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private static final boolean SHOW_SPEED_IN_BITS = false;
private TrafficSpeedMeasurer mTrafficSpeedMeasurer;
private TextView mTextView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.connection_class);
mTrafficSpeedMeasurer = new TrafficSpeedMeasurer(TrafficSpeedMeasurer.TrafficType.ALL);
mTrafficSpeedMeasurer.startMeasuring();
}
#Override
protected void onDestroy() {
super.onDestroy();
mTrafficSpeedMeasurer.stopMeasuring();
}
#Override
protected void onPause() {
super.onPause();
mTrafficSpeedMeasurer.removeListener(mStreamSpeedListener);
}
#Override
protected void onResume() {
super.onResume();
mTrafficSpeedMeasurer.registerListener(mStreamSpeedListener);
}
private ITrafficSpeedListener mStreamSpeedListener = new ITrafficSpeedListener() {
#Override
public void onTrafficSpeedMeasured(final double upStream, final double downStream) {
runOnUiThread(new Runnable() {
#Override
public void run() {
String upStreamSpeed = Utils.parseSpeed(upStream, SHOW_SPEED_IN_BITS);
String downStreamSpeed = Utils.parseSpeed(downStream, SHOW_SPEED_IN_BITS);
mTextView.setText("Up Stream Speed: " + upStreamSpeed + "\n" + "Down Stream Speed: " + downStreamSpeed);
}
});
}
};
}
TrafficSpeedMeasurer.java
import android.net.TrafficStats;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
public class TrafficSpeedMeasurer {
private ITrafficSpeedListener mTrafficSpeedListener;
private SamplingHandler mHandler;
private TrafficType mTrafficType;
private long mLastTimeReading;
private long mPreviousUpStream = -1;
private long mPreviousDownStream = -1;
public TrafficSpeedMeasurer(TrafficType trafficType) {
mTrafficType = trafficType;
HandlerThread thread = new HandlerThread("ParseThread");
thread.start();
mHandler = new SamplingHandler(thread.getLooper());
}
public void registerListener(ITrafficSpeedListener iTrafficSpeedListener) {
mTrafficSpeedListener = iTrafficSpeedListener;
}
public void removeListener() {
mTrafficSpeedListener = null;
}
public void startMeasuring() {
mHandler.startSamplingThread();
mLastTimeReading = SystemClock.elapsedRealtime();
}
public void stopMeasuring() {
mHandler.stopSamplingThread();
finalReadTrafficStats();
}
private void readTrafficStats() {
long newBytesUpStream = (mTrafficType == TrafficType.MOBILE ? TrafficStats.getMobileTxBytes() : TrafficStats.getTotalTxBytes()) * 1024;
long newBytesDownStream = (mTrafficType == TrafficType.MOBILE ? TrafficStats.getMobileRxBytes() : TrafficStats.getTotalRxBytes()) * 1024;
long byteDiffUpStream = newBytesUpStream - mPreviousUpStream;
long byteDiffDownStream = newBytesDownStream - mPreviousDownStream;
synchronized (this) {
long currentTime = SystemClock.elapsedRealtime();
double bandwidthUpStream = 0;
double bandwidthDownStream = 0;
if (mPreviousUpStream >= 0) {
bandwidthUpStream = (byteDiffUpStream) * 1.0 / (currentTime - mLastTimeReading);
}
if (mPreviousDownStream >= 0) {
bandwidthDownStream = (byteDiffDownStream) * 1.0 / (currentTime - mLastTimeReading);
}
if (mTrafficSpeedListener != null) {
mTrafficSpeedListener.onTrafficSpeedMeasured(bandwidthUpStream, bandwidthDownStream);
}
mLastTimeReading = currentTime;
}
mPreviousDownStream = newBytesDownStream;
mPreviousUpStream = newBytesUpStream;
}
private void finalReadTrafficStats() {
readTrafficStats();
mPreviousUpStream = -1;
mPreviousDownStream = -1;
}
private class SamplingHandler extends Handler {
private static final long SAMPLE_TIME = 1000;
private static final int MSG_START = 1;
private SamplingHandler(Looper looper) {
super(looper);
}
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_START:
readTrafficStats();
sendEmptyMessageDelayed(MSG_START, SAMPLE_TIME);
break;
default:
throw new IllegalArgumentException("Unknown what=" + msg.what);
}
}
void startSamplingThread() {
sendEmptyMessage(SamplingHandler.MSG_START);
}
void stopSamplingThread() {
removeMessages(SamplingHandler.MSG_START);
}
}
public enum TrafficType {
MOBILE,
ALL
}
}
ITrafficSpeedListener.java
public interface ITrafficSpeedListener {
void onTrafficSpeedMeasured(double upStream, double downStream);
}
Utils.java
import java.util.Locale;
public class Utils {
private static final long B = 1;
private static final long KB = B * 1024;
private static final long MB = KB * 1024;
private static final long GB = MB * 1024;
public static String parseSpeed(double bytes, boolean inBits) {
double value = inBits ? bytes * 8 : bytes;
if (value < KB) {
return String.format(Locale.getDefault(), "%.1f " + (inBits ? "b" : "B") + "/s", value);
} else if (value < MB) {
return String.format(Locale.getDefault(), "%.1f K" + (inBits ? "b" : "B") + "/s", value / KB);
} else if (value < GB) {
return String.format(Locale.getDefault(), "%.1f M" + (inBits ? "b" : "B") + "/s", value / MB);
} else {
return String.format(Locale.getDefault(), "%.2f G" + (inBits ? "b" : "B") + "/s", value / GB);
}
}
}
.
Visual Result
What you're trying to determine is the transfer rate of the bytes being uploaded over your HTTP Client. Obviously, this depends on the HTTP client you're using.
There's no out-of-the-box solution which applies to all HTTP clients used on Android. The Android SDK does not provide any methods for you to determine the transfer rate of a particular upload.
Fortunately, you're using OKHttp and there is a relatively straight-forward way to do this. You're going to have to implement a custom RequestBody, and observe the bytes being written to the buffer when the request is in flight.
There's a 'recipe' for doing this on the OkHttp Github:
https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/Progress.java
You could also refer to this StackOverflow question dealing with the exact same topic:
Tracking progress of multipart file upload using OKHTTP
Another here:
OKHTTP 3 Tracking Multipart upload progress
I am talking in the context of your app since this makes it easier to capture the real time speed of your uploaded data. You don't need any extra libraries or sdk api's.
You are presumably uploading the data in chunks to the server. So
a) You know the data size of each packet
b) You know the start time before sending the packet / before sending multiple packets
c) You know the end time of xy packets by the server response e.g. status 200
With that you have all parameters to calculate the upload speed
double uploadSpeed = packet.size / (endTime - startTime) // time * 1000 to have it in seconds
EDIT:
Since you are using MultiPart from OkHttp you can monitor the amount of bytes uploaded. Tracking progress of multipart file upload using OKHTTP. You would replace packet.size with the current uploaded amount and the endTime would be an interval of xy seconds.
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();
}
}
}
In my Android app. I want to support barcode scanner for items. However, I used the Zxing project that provides a standalone barcode reader application which (via Android's intent mechanism ) can be called by other applications who wish to integrate barcode scanning.
Everything goes great, I scan barcode then check if any item from database exist or not to display it with its Info. Unluckily, when I attempt to scan Industrial 2 of 5 barcode (which a special type of barcodes) it is not work with me! it seems Zxing project does not support it!
The codes for Zxing project are:
IntentIntegrator class:
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.integration.android;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
/**
* <p>A utility class which helps ease integration with Barcode Scanner via {#link Intent}s. This is a simple
* way to invoke barcode scanning and receive the result, without any need to integrate, modify, or learn the
* project's source code.</p>
*
* <h2>Initiating a barcode scan</h2>
*
* <p>To integrate, create an instance of {#code IntentIntegrator} and call {#link #initiateScan()} and wait
* for the result in your app.</p>
*
* <p>It does require that the Barcode Scanner (or work-alike) application is installed. The
* {#link #initiateScan()} method will prompt the user to download the application, if needed.</p>
*
* <p>There are a few steps to using this integration. First, your {#link Activity} must implement
* the method {#link Activity#onActivityResult(int, int, Intent)} and include a line of code like this:</p>
*
* <pre>{#code
* public void onActivityResult(int requestCode, int resultCode, Intent intent) {
* IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
* if (scanResult != null) {
* // handle scan result
* }
* // else continue with any other code you need in the method
* ...
* }
* }</pre>
*
* <p>This is where you will handle a scan result.</p>
*
* <p>Second, just call this in response to a user action somewhere to begin the scan process:</p>
*
* <pre>{#code
* IntentIntegrator integrator = new IntentIntegrator(yourActivity);
* integrator.initiateScan();
* }</pre>
*
* <p>Note that {#link #initiateScan()} returns an {#link AlertDialog} which is non-null if the
* user was prompted to download the application. This lets the calling app potentially manage the dialog.
* In particular, ideally, the app dismisses the dialog if it's still active in its {#link Activity#onPause()}
* method.</p>
*
* <p>You can use {#link #setTitle(String)} to customize the title of this download prompt dialog (or, use
* {#link #setTitleByID(int)} to set the title by string resource ID.) Likewise, the prompt message, and
* yes/no button labels can be changed.</p>
*
* <p>Finally, you can use {#link #addExtra(String, Object)} to add more parameters to the Intent used
* to invoke the scanner. This can be used to set additional options not directly exposed by this
* simplified API.</p>
*
* <p>By default, this will only allow applications that are known to respond to this intent correctly
* do so. The apps that are allowed to response can be set with {#link #setTargetApplications(List)}.
* For example, set to {#link #TARGET_BARCODE_SCANNER_ONLY} to only target the Barcode Scanner app itself.</p>
*
* <h2>Sharing text via barcode</h2>
*
* <p>To share text, encoded as a QR Code on-screen, similarly, see {#link #shareText(CharSequence)}.</p>
*
* <p>Some code, particularly download integration, was contributed from the Anobiit application.</p>
*
* <h2>Enabling experimental barcode formats</h2>
*
* <p>Some formats are not enabled by default even when scanning with {#link #ALL_CODE_TYPES}, such as
* PDF417. Use {#link #initiateScan(java.util.Collection)} with
* a collection containing the names of formats to scan for explicitly, like "PDF_417", to use such
* formats.</p>
*
* #author Sean Owen
* #author Fred Lin
* #author Isaac Potoczny-Jones
* #author Brad Drehmer
* #author gcstang
*/
public class IntentIntegrator {
public static final int REQUEST_CODE = 0x0000c0de; // Only use bottom 16 bits
private static final String TAG = IntentIntegrator.class.getSimpleName();
public static final String DEFAULT_TITLE = "Install Barcode Scanner?";
public static final String DEFAULT_MESSAGE =
"This application requires Barcode Scanner. Would you like to install it?";
public static final String DEFAULT_YES = "Yes";
public static final String DEFAULT_NO = "No";
private static final String BS_PACKAGE = "com.google.zxing.client.android";
private static final String BSPLUS_PACKAGE = "com.srowen.bs.android";
// supported barcode formats
public static final Collection<String> PRODUCT_CODE_TYPES = list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "RSS_14");
public static final Collection<String> ONE_D_CODE_TYPES =
list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "CODE_39", "CODE_93", "CODE_128",
"ITF", "RSS_14", "RSS_EXPANDED");
public static final Collection<String> QR_CODE_TYPES = Collections.singleton("QR_CODE");
public static final Collection<String> DATA_MATRIX_TYPES = Collections.singleton("DATA_MATRIX");
public static final Collection<String> ALL_CODE_TYPES = null;
public static final List<String> TARGET_BARCODE_SCANNER_ONLY = Collections.singletonList(BS_PACKAGE);
public static final List<String> TARGET_ALL_KNOWN = list(
BS_PACKAGE, // Barcode Scanner
BSPLUS_PACKAGE, // Barcode Scanner+
BSPLUS_PACKAGE + ".simple" // Barcode Scanner+ Simple
// What else supports this intent?
);
private final Activity activity;
private String title;
private String message;
private String buttonYes;
private String buttonNo;
private List<String> targetApplications;
private final Map<String,Object> moreExtras;
public IntentIntegrator(Activity activity) {
this.activity = activity;
title = DEFAULT_TITLE;
message = DEFAULT_MESSAGE;
buttonYes = DEFAULT_YES;
buttonNo = DEFAULT_NO;
targetApplications = TARGET_ALL_KNOWN;
moreExtras = new HashMap<String,Object>(3);
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public void setTitleByID(int titleID) {
title = activity.getString(titleID);
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public void setMessageByID(int messageID) {
message = activity.getString(messageID);
}
public String getButtonYes() {
return buttonYes;
}
public void setButtonYes(String buttonYes) {
this.buttonYes = buttonYes;
}
public void setButtonYesByID(int buttonYesID) {
buttonYes = activity.getString(buttonYesID);
}
public String getButtonNo() {
return buttonNo;
}
public void setButtonNo(String buttonNo) {
this.buttonNo = buttonNo;
}
public void setButtonNoByID(int buttonNoID) {
buttonNo = activity.getString(buttonNoID);
}
public Collection<String> getTargetApplications() {
return targetApplications;
}
public final void setTargetApplications(List<String> targetApplications) {
if (targetApplications.isEmpty()) {
throw new IllegalArgumentException("No target applications");
}
this.targetApplications = targetApplications;
}
public void setSingleTargetApplication(String targetApplication) {
this.targetApplications = Collections.singletonList(targetApplication);
}
public Map<String,?> getMoreExtras() {
return moreExtras;
}
public final void addExtra(String key, Object value) {
moreExtras.put(key, value);
}
/**
* Initiates a scan for all known barcode types.
*/
public final AlertDialog initiateScan() {
return initiateScan(ALL_CODE_TYPES);
}
/**
* Initiates a scan only for a certain set of barcode types, given as strings corresponding
* to their names in ZXing's {#code BarcodeFormat} class like "UPC_A". You can supply constants
* like {#link #PRODUCT_CODE_TYPES} for example.
*
* #return the {#link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise
*/
public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats) {
Intent intentScan = new Intent(BS_PACKAGE + ".SCAN");
intentScan.addCategory(Intent.CATEGORY_DEFAULT);
// check which types of codes to scan for
if (desiredBarcodeFormats != null) {
// set the desired barcode types
StringBuilder joinedByComma = new StringBuilder();
for (String format : desiredBarcodeFormats) {
if (joinedByComma.length() > 0) {
joinedByComma.append(',');
}
joinedByComma.append(format);
}
intentScan.putExtra("SCAN_FORMATS", joinedByComma.toString());
}
String targetAppPackage = findTargetAppPackage(intentScan);
if (targetAppPackage == null) {
return showDownloadDialog();
}
intentScan.setPackage(targetAppPackage);
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
attachMoreExtras(intentScan);
startActivityForResult(intentScan, REQUEST_CODE);
return null;
}
/**
* Start an activity.<br>
* This method is defined to allow different methods of activity starting for
* newer versions of Android and for compatibility library.
*
* #param intent Intent to start.
* #param code Request code for the activity
* #see android.app.Activity#startActivityForResult(Intent, int)
* #see android.app.Fragment#startActivityForResult(Intent, int)
*/
protected void startActivityForResult(Intent intent, int code) {
activity.startActivityForResult(intent, code);
}
private String findTargetAppPackage(Intent intent) {
PackageManager pm = activity.getPackageManager();
List<ResolveInfo> availableApps = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (availableApps != null) {
for (ResolveInfo availableApp : availableApps) {
String packageName = availableApp.activityInfo.packageName;
if (targetApplications.contains(packageName)) {
return packageName;
}
}
}
return null;
}
private AlertDialog showDownloadDialog() {
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity);
downloadDialog.setTitle(title);
downloadDialog.setMessage(message);
downloadDialog.setPositiveButton(buttonYes, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
String packageName = targetApplications.get(0);
Uri uri = Uri.parse("market://details?id=" + packageName);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
try {
activity.startActivity(intent);
} catch (ActivityNotFoundException anfe) {
// Hmm, market is not installed
Log.w(TAG, "Google Play is not installed; cannot install " + packageName);
}
}
});
downloadDialog.setNegativeButton(buttonNo, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {}
});
return downloadDialog.show();
}
/**
* <p>Call this from your {#link Activity}'s
* {#link Activity#onActivityResult(int, int, Intent)} method.</p>
*
* #return null if the event handled here was not related to this class, or
* else an {#link IntentResult} containing the result of the scan. If the user cancelled scanning,
* the fields will be null.
*/
public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
String contents = intent.getStringExtra("SCAN_RESULT");
String formatName = intent.getStringExtra("SCAN_RESULT_FORMAT");
byte[] rawBytes = intent.getByteArrayExtra("SCAN_RESULT_BYTES");
int intentOrientation = intent.getIntExtra("SCAN_RESULT_ORIENTATION", Integer.MIN_VALUE);
Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation;
String errorCorrectionLevel = intent.getStringExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL");
return new IntentResult(contents,
formatName,
rawBytes,
orientation,
errorCorrectionLevel);
}
return new IntentResult();
}
return null;
}
/**
* Defaults to type "TEXT_TYPE".
* #see #shareText(CharSequence, CharSequence)
*/
public final AlertDialog shareText(CharSequence text) {
return shareText(text, "TEXT_TYPE");
}
/**
* Shares the given text by encoding it as a barcode, such that another user can
* scan the text off the screen of the device.
*
* #param text the text string to encode as a barcode
* #param type type of data to encode. See {#code com.google.zxing.client.android.Contents.Type} constants.
* #return the {#link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise
*/
public final AlertDialog shareText(CharSequence text, CharSequence type) {
Intent intent = new Intent();
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setAction(BS_PACKAGE + ".ENCODE");
intent.putExtra("ENCODE_TYPE", type);
intent.putExtra("ENCODE_DATA", text);
String targetAppPackage = findTargetAppPackage(intent);
if (targetAppPackage == null) {
return showDownloadDialog();
}
intent.setPackage(targetAppPackage);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
attachMoreExtras(intent);
activity.startActivity(intent);
return null;
}
private static List<String> list(String... values) {
return Collections.unmodifiableList(Arrays.asList(values));
}
private void attachMoreExtras(Intent intent) {
for (Map.Entry<String,Object> entry : moreExtras.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
// Kind of hacky
if (value instanceof Integer) {
intent.putExtra(key, (Integer) value);
} else if (value instanceof Long) {
intent.putExtra(key, (Long) value);
} else if (value instanceof Boolean) {
intent.putExtra(key, (Boolean) value);
} else if (value instanceof Double) {
intent.putExtra(key, (Double) value);
} else if (value instanceof Float) {
intent.putExtra(key, (Float) value);
} else if (value instanceof Bundle) {
intent.putExtra(key, (Bundle) value);
} else {
intent.putExtra(key, value.toString());
}
}
}
}
IntentResult class:
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.integration.android;
/**
* <p>Encapsulates the result of a barcode scan invoked through {#link IntentIntegrator}.</p>
*
* #author Sean Owen
*/
public final class IntentResult {
private final String contents;
private final String formatName;
private final byte[] rawBytes;
private final Integer orientation;
private final String errorCorrectionLevel;
IntentResult() {
this(null, null, null, null, null);
}
IntentResult(String contents,
String formatName,
byte[] rawBytes,
Integer orientation,
String errorCorrectionLevel) {
this.contents = contents;
this.formatName = formatName;
this.rawBytes = rawBytes;
this.orientation = orientation;
this.errorCorrectionLevel = errorCorrectionLevel;
}
/**
* #return raw content of barcode
*/
public String getContents() {
return contents;
}
/**
* #return name of format, like "QR_CODE", "UPC_A". See {#code BarcodeFormat} for more format names.
*/
public String getFormatName() {
return formatName;
}
/**
* #return raw bytes of the barcode content, if applicable, or null otherwise
*/
public byte[] getRawBytes() {
return rawBytes;
}
/**
* #return rotation of the image, in degrees, which resulted in a successful scan. May be null.
*/
public Integer getOrientation() {
return orientation;
}
/**
* #return name of the error correction level used in the barcode, if applicable
*/
public String getErrorCorrectionLevel() {
return errorCorrectionLevel;
}
#Override
public String toString() {
StringBuilder dialogText = new StringBuilder(100);
dialogText.append("Format: ").append(formatName).append('\n');
dialogText.append("Contents: ").append(contents).append('\n');
int rawBytesLength = rawBytes == null ? 0 : rawBytes.length;
dialogText.append("Raw bytes: (").append(rawBytesLength).append(" bytes)\n");
dialogText.append("Orientation: ").append(orientation).append('\n');
dialogText.append("EC level: ").append(errorCorrectionLevel).append('\n');
return dialogText.toString();
}
}
To call Zxing in desired Activity:
//instantiate ZXing integration class
IntentIntegrator scanIntegrator = new IntentIntegrator(this);
//start scanning
scanIntegrator.initiateScan();
To get scanning result by:
if (scanningResult != null) {
//get content from Intent Result
String scanContent = scanningResult.getContents();
//get format name of data scanned
String scanFormat = scanningResult.getFormatName();
}
Industrial 2 of 5 barcode example:
I see some barcodes scanner support Industrial 2 of 5 barcode as Accusoft Barcode Scanner; but I used Zxing cause it is available for developer to include it in project!
I see like http://www.onbarcode.com/products/android_barcode/barcodes/code_2_of_5.html and others for Zxing project but I did not find any way can help me!
In sum: Is there any way to support Industrial 2 of 5 barcode in Zxing project? or may there is any way to support it by another project rather than Zxing?
sorry for this long question! any suggestions or help will be appreciated, thanks in advance !
Zxing doesnt support this format ! Used our own app which uses zxing....
Everything in the code seems fine. I do not understand why it is giving me this error.
Using Eclipse IDE (Juno), I've clicked "Run" and got the following message on the console:
Error: Could not find or load main class JavaFixHistoryMiner
This was an imported file, I also added an external library
/**
* Example of how to request and process historical rate data from the Java API
*
* #author rkichenama
*/
public class JavaFixHistoryMiner
implements IGenericMessageListener, IStatusMessageListener
{
private static final String server = "http://www.fxcorporate.com/Hosts.jsp";
private static final String TEST_CURRENCY = "EUR/USD";
private FXCMLoginProperties login;
private IGateway gateway;
private String currentRequest;
private boolean requestComplete;
private ArrayList<CollateralReport> accounts = new ArrayList<CollateralReport>();
private HashMap<UTCDate, MarketDataSnapshot> historicalRates = new HashMap<UTCDate, MarketDataSnapshot>();
private static PrintWriter output = new PrintWriter((OutputStream)System.out, true);
public PrintWriter getOutput() { return output; }
public void setOutput(PrintWriter newOutput) { output = newOutput; }
/**
* Creates a new JavaFixHistoryMiner with credentials with configuration file
*
* #param username
* #param password
* #param terminal - which terminal to login into, dependent on the type of account, case sensitive
* #param server - url, like 'http://www.fxcorporate.com/Hosts.jsp'
* #param file - a local file used to define configuration
*/
public JavaFixHistoryMiner(String username, String password, String terminal, String file)
{
// if file is not specified
if(file == null)
// create a local LoginProperty
this.login = new FXCMLoginProperties(username, password, terminal, server);
else
this.login = new FXCMLoginProperties(username, password, terminal, server, file);
}
/**
* Creates a new JavaFixHistoryMiner with credentials and no configuration file
*
* #param username
* #param password
* #param terminal - which terminal to login into, dependent on the type of account, case sensitive
* #param server - url, like 'http://www.fxcorporate.com/Hosts.jsp'
*/
public JavaFixHistoryMiner(String username, String password, String terminal)
{
// call the proper constructor
this(username, password, terminal, null);
}
public JavaFixHistoryMiner(String[] args)
{
// call the proper constructor
this(args[0], args[1], args[2], null);
}
/**
* Attempt to login with credentials supplied in constructor, assigning self as listeners
*/
public boolean login()
{
return this.login(this, this);
}
/**
* Attempt to login with credentials supplied in constructor
*
* #param genericMessageListener - the listener object for trading events
* #param statusMessageListener - the listener object for status events
*
* #return true if login successful, false if not
*/
public boolean login(IGenericMessageListener genericMessageListener, IStatusMessageListener statusMessageListener)
{
try
{
// if the gateway has not been defined
if(gateway == null)
// assign it to a new gateway created by the factory
gateway = GatewayFactory.createGateway();
// register the generic message listener with the gateway
gateway.registerGenericMessageListener(genericMessageListener);
// register the status message listener with the gateway
gateway.registerStatusMessageListener(statusMessageListener);
// if the gateway has not been connected
if(!gateway.isConnected())
{
// attempt to login with the local login properties
gateway.login(this.login);
}
else
{
// attempt to re-login to the api
gateway.relogin();
}
// set the state of the request to be incomplete
requestComplete = false;
// request the current trading session status
currentRequest = gateway.requestTradingSessionStatus();
// wait until the request is complete
while(!requestComplete) {}
// return that this process was successful
return true;
}
catch(Exception e) { e.printStackTrace(); }
// if any error occurred, then return that this process failed
return false;
}
/**
* Attempt to logout, assuming that the supplied listeners reference self
*/
public void logout()
{
this.logout(this, this);
}
/**
* Attempt to logout, removing the supplied listeners prior to disconnection
*
* #param genericMessageListener - the listener object for trading events
* #param statusMessageListener - the listener object for status events
*/
public void logout(IGenericMessageListener genericMessageListener, IStatusMessageListener statusMessageListener)
{
// attempt to logout of the api
gateway.logout();
// remove the generic message listener, stop listening to updates
gateway.removeGenericMessageListener(genericMessageListener);
// remove the status message listener, stop listening to status changes
gateway.removeStatusMessageListener(statusMessageListener);
}
/**
* Request a refresh of the collateral reports under the current login
*/
public void retrieveAccounts()
{
// if the gateway is null then attempt to login
if(gateway == null) this.login();
// set the state of the request to be incomplete
requestComplete = false;
// request the refresh of all collateral reports
currentRequest = gateway.requestAccounts();
// wait until all the reqports have been processed
while(!requestComplete) {}
}
/**
* Send a fully formed order to the API and wait for the response.
*
* #return the market order number of placed trade, NONE if the trade did not execute, null on error
*/
public String sendRequest(ITransportable request)
{
try
{
// set the completion status of the requst to false
requestComplete = false;
// send the request message to the api
currentRequest = gateway.sendMessage(request);
// wait until the api answers on this particular request
// while(!requestComplete) {}
// if there is a value to return, it will be passed by currentResult
return currentRequest;
}
catch(Exception e) { e.printStackTrace(); }
// if an error occured, return no result
return null;
}
/**
* Implementing IStatusMessageListener to capture and process messages sent back from API
*
* #param status - status message received by API
*/
#Override public void messageArrived(ISessionStatus status)
{
// check to the status code
if(status.getStatusCode() == ISessionStatus.STATUSCODE_ERROR ||
status.getStatusCode() == ISessionStatus.STATUSCODE_DISCONNECTING ||
status.getStatusCode() == ISessionStatus.STATUSCODE_CONNECTING ||
status.getStatusCode() == ISessionStatus.STATUSCODE_CONNECTED ||
status.getStatusCode() == ISessionStatus.STATUSCODE_CRITICAL_ERROR ||
status.getStatusCode() == ISessionStatus.STATUSCODE_EXPIRED ||
status.getStatusCode() == ISessionStatus.STATUSCODE_LOGGINGIN ||
status.getStatusCode() == ISessionStatus.STATUSCODE_LOGGEDIN ||
status.getStatusCode() == ISessionStatus.STATUSCODE_PROCESSING ||
status.getStatusCode() == ISessionStatus.STATUSCODE_DISCONNECTED)
{
// display status message
output.println("\t\t" + status.getStatusMessage());
}
}
/**
* Implementing IGenericMessageListener to capture and process messages sent back from API
*
* #param message - message received for processing by API
*/
#Override public void messageArrived(ITransportable message)
{
// decide which child function to send an cast instance of the message
try
{
// if it is an instance of CollateralReport, process the collateral report
if(message instanceof CollateralReport) messageArrived((CollateralReport)message);
// if it is an instance of MarketDataSnapshot, process the historical data
if(message instanceof MarketDataSnapshot) messageArrived((MarketDataSnapshot)message);
// if it is an instance of MarketDataRequestReject, process the historical data request error
if(message instanceof MarketDataRequestReject) messageArrived((MarketDataRequestReject)message);
// if the message is an instance of TradingSessionStatus, cast it and send to child function
else if(message instanceof TradingSessionStatus) messageArrived((TradingSessionStatus)message);
}
catch(Exception e) { e.printStackTrace(output); }
}
/**
* Separate function to handle collateral report requests
*
* #param cr - message interpreted as an instance of CollateralReport
*/
public void messageArrived(CollateralReport cr)
{
// if this report is the result of a direct request by a waiting process
if(currentRequest.equals(cr.getRequestID()) && !accounts.contains(cr))
{
// add the trading account to the account list
accounts.add(cr);
// set the state of the request to be completed only if this is the last collateral report
// requested
requestComplete = cr.isLastRptRequested();
}
}
/**
/**
* Separate function to handle the trading session status updates and pull the trading instruments
*
* #param tss - the message interpreted as a TradingSessionStatus instance
*/
public void messageArrived(TradingSessionStatus tss)
{
// check to see if there is a request from main application for a session update
if(currentRequest.equals(tss.getRequestID()))
{
// set that the request is complete for any waiting thread
requestComplete = true;
// attempt to set up the historical market data request
try
{
// create a new market data request
MarketDataRequest mdr = new MarketDataRequest();
// set the subscription type to ask for only a snapshot of the history
mdr.setSubscriptionRequestType(SubscriptionRequestTypeFactory.SNAPSHOT);
// request the response to be formated FXCM style
mdr.setResponseFormat(IFixDefs.MSGTYPE_FXCMRESPONSE);
// set the intervale of the data candles
mdr.setFXCMTimingInterval(FXCMTimingIntervalFactory.MIN15);
// set the type set for the data candles
mdr.setMDEntryTypeSet(MarketDataRequest.MDENTRYTYPESET_ALL);
// configure the start and end dates
Date now = new Date();
Calendar calendar = (Calendar)Calendar.getInstance().clone();
calendar.setTime(now);
calendar.add(Calendar.DAY_OF_MONTH, -1);
Date beforeNow = calendar.getTime();
// set the dates and times for the market data request
mdr.setFXCMStartDate(new UTCDate(beforeNow));
mdr.setFXCMStartTime(new UTCTimeOnly(beforeNow));
mdr.setFXCMEndDate(new UTCDate(now));
mdr.setFXCMEndTime(new UTCTimeOnly(now));
// set the instrument on which the we want the historical data
mdr.addRelatedSymbol(tss.getSecurity(TEST_CURRENCY));
// send the request
sendRequest(mdr);
}
catch(Exception e) { e.printStackTrace(); }
}
}
/**
* Separate function to handle the rejection of a market data historical snapshot
*
* #param mdrr - message interpreted as an instance of MarketDataRequestReject
*/
public void messageArrived(MarketDataRequestReject mdrr)
{
// display note consisting of the reason the request was rejected
output.println("Historical data rejected; " + mdrr.getMDReqRejReason());
// set the state of the request to be complete
requestComplete = true;
}
/**
* Separate function to handle the receipt of market data snapshots
*
* Current dealing rates are retrieved through the same class as historical requests. The difference
* is that historical requests are 'answers' to a specific request.
*
* #param mds
*/
public void messageArrived(MarketDataSnapshot mds)
{
// if the market data snapshot is part of the answer to a specific request
try
{
if(mds.getRequestID() != null && mds.getRequestID().equals(currentRequest))
{
// add that snapshot to the historicalRates table
synchronized(historicalRates) { historicalRates.put(mds.getDate(), mds); }
// set the request to be complete only if the continuous flaf is at the end
requestComplete = (mds.getFXCMContinuousFlag() == IFixDefs.FXCMCONTINUOUS_END);
}
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Display the historical rates captured
*/
public void displayHistory()
{
// give the table a header
output.println("Rate 15 minute candle History for " + TEST_CURRENCY);
// give the table column headings
output.println("Date\t Time\t\tOBid\tCBid\tHBid\tLBid");
// get the keys for the historicalRates table into a sorted list
SortedSet<UTCDate> candle = new TreeSet<UTCDate>(historicalRates.keySet());
// define a format for the dates
SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss z");
// make the date formatter above convert from GMT to EST
sdf.setTimeZone(TimeZone.getTimeZone("EST"));
// go through the keys of the historicalRates table
for(int i = 0; i < candle.size(); i++)
{
// create a single instance of the snapshot
MarketDataSnapshot candleData;
synchronized(historicalRates) { candleData = historicalRates.get(candle.toArray()[i]); }
// convert the key to a Date
Date candleDate = ((UTCDate)candle.toArray()[i]).toDate();
// print out the historicalRate table data
output.println(
sdf.format(candleDate) + "\t" + // the date and time formatted and converted to EST
candleData.getBidOpen() + "\t" + // the open bid for the candle
candleData.getBidClose() + "\t" + // the close bid for the candle
candleData.getBidHigh() + "\t" + // the high bid for the candle
candleData.getBidLow()); // the low bid for the candle
}
// repeat the table column headings
output.println("Date\t Time\t\tOBid\tCBid\tHBid\tLBid");
}
public static void main(String[] args)
{
try
{
// create an instance of the JavaFixHistoryMiner
JavaFixHistoryMiner miner = new JavaFixHistoryMiner("rkichenama", "1311016", "Demo");
// login to the api
miner.login();
// retrieve the trader accounts to ensure login process is complete
miner.retrieveAccounts();
// display nore that the history display is delayed
// partially for theatrics, partially to ensure all the rates are collected
output.println("Displaying history in");
// wait ~ 2.5 seconds
for(int i = 5; i > 0; i--)
{
output.println(i + "...");
Thread.sleep(500);
}
// display the collected rates
miner.displayHistory();
// log out of the api
miner.logout();
}
catch (Exception e) { e.printStackTrace(); }
}
}
I've recently run into a strange bug with playing midis in a game I'm developing. I thought my midi code was working fine since it used to play midis without sounding weird. Now whenever it plays midis, they sound all tinny, echo-y, and loud.
I haven't touched my midi player code for a very long time, so I'm wondering if it's possible that a recent Java update has exposed a bug that has been in my code all along. Or perhaps there's some sort of midi bug in my version of Java that I'm not aware of?
The midis sound fine whenever I play them outside of my game.
I'm running Java 6, update 31, build 1.6.0_31-b05. Here's a SSCCE that reproduces the problem (it reproduces it on my JVM at least):
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import javax.sound.midi.*;
import java.net.URL;
public class MidiSSCCE extends JFrame
{
public MidiSSCCE()
{
super("Sound problem SSCCE");
this.setSize(200,100);
// instantiate main window panel
JPanel screenP = new SSCCEPanel(this);
this.add(screenP);
// finishing touches on Game window
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
System.out.println("Game Window successfully created!!!");
}
public static void main(String[] args)
{
MidiSSCCE gui = new MidiSSCCE();
}
}
/**
* SSCCEPanel is the JPanel that manages the example's timer, painting, and logic.
**/
class SSCCEPanel extends JPanel
{
public Frame parentFrame;
private Timer timer;
public int logicLoops;
public double prevFPS;
boolean timerReady;
// The MidiPlayer object is used by the example to play the midi.
public MidiPlayer midiPlayer;
public SSCCEPanel(Frame parent)
{
super(true);
parentFrame = parent;
this.setFocusable(true);
Toolkit.getDefaultToolkit().sync();
logicLoops = 0;
midiPlayer = new MidiPlayer();
TimerListener timerListener = new TimerListener();
prevFPS = 0;
timerReady = true;
timer = new Timer(0,timerListener);
this.setFPS(60);
timer.start();
}
/**
* setFPS()
* Preconditions: fps is a quantity of frames per second
* Postconditions: Sets the timer's refresh rate so that it
* fires fps times per second.
**/
public void setFPS(int fps)
{
int mspf = (int) (1000.0 /fps + 0.5);
timer.setDelay(mspf);
}
/**
* This is the JPanel's timer listener. It runs the example's logic and repaint
* methods each time it gets a timer signal.
**/
private class TimerListener implements ActionListener
{
long startTime = System.currentTimeMillis();
long lastTime = this.startTime;
int ticks = 0;
public void actionPerformed(ActionEvent e)
{
Object source = e.getSource();
if(source == timer)
{
// perform a loop through the game's logic and repaint.
synchronized(this)
{
if(timerReady)
{
timerReady = false;
runSSCCELogic();
repaint();
timerReady = true;
}
}
// Logic for Frames per Second counter
this.ticks++;
long currentTime = System.currentTimeMillis();
if(currentTime - startTime >= 500)
{
prevFPS = 1000.0 * ticks/(1.0*currentTime - startTime);
System.out.println(prevFPS);
startTime = currentTime;
ticks = 0;
}
lastTime = currentTime;
}
}
}
/**
* repaints the SSCCE.
* This just shows the current FPS.
**/
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2D = (Graphics2D) g;
double roundedFPS = Math.round(prevFPS*10)/10.0;
g2D.setColor(new Color(0x000000));
g2D.drawString("FPS: " + roundedFPS, 20,20);
g.dispose();
}
/**
* runSSCCEELogic()
* This is where the run-time logic for the SSCCE example is.
* All it does is load and play a midi called "mymidi.mid" which is located in the same directory.
**/
public void runSSCCELogic()
{
if(logicLoops == 1)
{
midiPlayer.load("http://www.vgmusic.com/music/computer/microsoft/windows/touhou_6_stage3_boss.mid");
midiPlayer.play(true);
}
logicLoops++;
}
}
/**
* MidiPlayer
* A class that allows midi files to be loaded and played.
**/
class MidiPlayer
{
private Sequence seq;
private Sequencer seqr;
private Synthesizer synth;
private Receiver receiver;
private File midiFile;
private String midiID;
private boolean loaded;
private boolean usingHardwareSoundbank;
// CONSTRUCTORS
public MidiPlayer()
{
loaded = false;
try
{
seqr = MidiSystem.getSequencer();
synth = MidiSystem.getSynthesizer();
}
catch(Exception e)
{
System.out.println("MIDI error: It appears your system doesn't have a MIDI device or your device is not working.");
}
}
/**
* MidiPlayer(String fileName)
* Constructor that also loads an initial midi file.
* Preconditions: fileName is the name of the midi file to be loaded.
* Postconditions: The MidiPlayer is created and loaded with the midi specified by fileName.
**/
public MidiPlayer(String fileName)
{
this();
load(fileName);
}
// DATA METHODS
/**
* load(String fileName)
* loads a midi file into this MidiPlayer.
* Preconditions: fileName is the name of the midi file to be loaded.
* Postconditions: fileName is loaded and is ready to be played.
**/
public void load(String fileName)
{
this.unload();
try
{
URL midiURL = new URL(fileName);
// midiFile = new File(fileName);
seq = MidiSystem.getSequence(midiURL);
seqr.open();
synth.open();
System.out.println("MidiDeviceInfo: ");
for(MidiDevice.Info info : MidiSystem.getMidiDeviceInfo())
{
System.out.println("\t" + info);
}
System.out.println();
if(synth.getDefaultSoundbank() == null)
{
receiver = MidiSystem.getReceiver();
usingHardwareSoundbank = true;
System.out.println("using hardware soundbank");
}
else
{
receiver = synth.getReceiver();
usingHardwareSoundbank = false;
System.out.println("using default software soundbank:" + synth.getDefaultSoundbank());
}
seqr.getTransmitter().setReceiver(receiver);
seqr.setSequence(seq);
loaded = true;
}
catch(IOException ioe)
{
System.out.println("MIDI error: Problem occured while reading " + midiFile.getName() + ".");
}
catch(InvalidMidiDataException imde)
{
System.out.println("MIDI error: " + midiFile.getName() + " is not a valid MIDI file or is unreadable.");
}
catch(Exception e)
{
System.out.println("MIDI error: Unexplained error occured while loading midi.");
}
}
/**
* unload()
* Unloads the current midi from the MidiPlayer and releases its resources from memory.
**/
public void unload()
{
this.stop();
seqr.close();
midiFile = null;
loaded = false;
}
// OTHER METHODS
/**
* setMidiID(String id)
* associates a String ID with the current midi.
* Preconditions: id is the ID we are associating with the current midi.
**/
public void setMidiID(String id)
{
midiID = id;
}
/**
* getMidiID(String id)
*
**/
public String getMidiID()
{
return new String(midiID);
}
/**
* play(boolean reset)
* plays the currently loaded midi.
* Preconditions: reset tells our midi whether or nor to begin playing from the start of the midi file's current loop start point.
* Postconditions: If reset is true, then the loaded midi begins playing from its loop start point (default 0).
* If reset is false, then the loaded midi resumes playing from its current position.
**/
public void play(boolean reset)
{
if(reset)
seqr.setTickPosition(seqr.getLoopStartPoint());
seqr.start();
}
/**
* stop()
* Pauses the current midi if it was playing.
**/
public void stop()
{
if(seqr.isOpen())
seqr.stop();
}
/**
* isRunning()
* Returns true if the current midi is playing. Returns false otherwise.
**/
public boolean isRunning()
{
return seqr.isRunning();
}
/**
* loop(int times)
* Sets the current midi to loop from start to finish a specific number of times.
* Preconditions: times is the number of times we want our midi to loop.
* Postconditions: The current midi is set to loop times times.
* If times = -1, the current midi will be set to loop infinitely.
**/
public void loop(int times)
{
loop(times,0,-1);
}
/**
* loop(int times)
* Sets the current midi to loop from a specified start point to a specified end point a specific number of times.
* Preconditions: times is the number of times we want our midi to loop.
* start is our loop's start point in ticks.
* end is our loop's end point in ticks.
* Postconditions: The current midi is set to loop from tick start to tick end times times.
* If times = -1, the current midi will be set to loop infinitely.
**/
public void loop(int times, long start, long end)
{
if(start < 0)
start = 0;
if(end > seqr.getSequence().getTickLength() || end <= 0)
end = seqr.getSequence().getTickLength();
if(start >= end && end != -1)
start = end-1;
seqr.setLoopStartPoint(start);
seqr.setLoopEndPoint(end);
if(times == -1)
seqr.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
else
seqr.setLoopCount(times);
}
public void setVolume(double vol)
{
try
{
if(usingHardwareSoundbank)
{
ShortMessage volumeMessage = new ShortMessage();
for ( int i = 0; i < 16; i++ )
{
volumeMessage.setMessage( ShortMessage.CONTROL_CHANGE, i, 7, (int)(vol*127) );
receiver.send( volumeMessage, -1 );
}
}
else
{
MidiChannel[] channels = synth.getChannels();
for( int c = 0; channels != null && c < channels.length; c++ )
{
channels[c].controlChange( 7, (int)( vol*127) );
}
}
}
catch ( Exception e )
{
e.printStackTrace();
}
}
}
The sound quality of MIDI is dependent on the synth generating the sound. It shouldn't have anything to do with your code.
Most likely, it is an issue with your sound card, but that isn't always what generates the sound, especially these days. Under Windows, there is a software synth from Microsoft that does all that. In any case, this won't have anything to do with your code.
It turns out that the problem is with my JRE build. I tried running this simple example for playing midis that Andrew linked me to:
import javax.sound.midi.*;
import java.net.URL;
class PlayMidi {
public static void main(String[] args) throws Exception {
URL url = new URL("http://www.vgmusic.com/music/computer/microsoft/windows/touhou_6_stage3_boss.mid");
Sequence sequence = MidiSystem.getSequence(url);
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
sequencer.setSequence(sequence);
sequencer.start();
}
}
and the midi's sound quality still didn't improve. I ran both my SSCCE and the above minimal example using Java 6_31 and Java 6_32.
In conclusion, this is a problem inherent in Java 6_31 and Java 6_32. So, I guess I'm out of luck until Sun/Oracle releases the next JRE build which will hopefully has this problem resolved.
EDIT:
I just had a friend who has Java 6_31 installed test this out on his machine. He didn't notice any difference in sound quality when he ran my java example vs playing the midi outside of Java. So, it is could also be likely that the problem has something to do with my machine specifically instead of it being a bug in Java. However, another friend just tested it and was also experiencing the same problem I'm having.
In conclusion, the problem is either inherent in Java versions past 6_31, in the sound devices of some machines, or a combination of both. The problem is probably not worth pursuing any further in native java.
I made a change to my MidiPlayer code that seems to have resolved the problem for me. I moved the code where it loads the soundbank and the transmitter's receiver to the constructor instead of in the method where it loads a midi. It no longer sounds really loud and tinny.
package gameEngine;
import javax.sound.midi.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* MidiPlayer
* author: Stephen Lindberg
* Last modified: Oct 14, 2011
*
* A class that allows midi files to be loaded and played.
**/
public class MidiPlayer
{
private Sequence seq;
private Sequencer seqr;
private Synthesizer synth;
private Receiver receiver;
private File midiFile;
private String midiID;
private boolean loaded;
private boolean usingHardwareSoundbank;
private float defaultTempo;
// CONSTRUCTORS
public MidiPlayer()
{
loaded = false;
try
{
seqr = MidiSystem.getSequencer();
synth = MidiSystem.getSynthesizer();
// print the user's midi device info
System.out.println("Setting up Midi Player...");
System.out.println("MidiDeviceInfo: ");
for(MidiDevice.Info info : MidiSystem.getMidiDeviceInfo())
{
System.out.println("\t" + info.getName() + ": " +info.getDescription());
}
System.out.println();
// obtain the receiver. This will be used for changing volume.
Soundbank soundbank = synth.getDefaultSoundbank();
if(soundbank == null)
{
receiver = MidiSystem.getReceiver();
usingHardwareSoundbank = true;
System.out.println("using hardware soundbank");
}
else
{
synth.loadAllInstruments(soundbank);
receiver = synth.getReceiver();
usingHardwareSoundbank = false;
System.out.println("using default software soundbank:" + soundbank);
}
seqr.getTransmitter().setReceiver(receiver);
}
catch(Exception e)
{
System.out.println("MIDI error: It appears your system doesn't have a MIDI device or your device is not working.");
}
}
/**
* MidiPlayer(String fileName)
* Constructor that also loads an initial midi file.
* Preconditions: fileName is the name of the midi file to be loaded.
* Postconditions: The MidiPlayer is created and loaded with the midi specified by fileName.
**/
public MidiPlayer(String fileName)
{
this();
load(fileName);
}
// DATA METHODS
/**
* load(String fileName)
* loads a midi file into this MidiPlayer.
* Preconditions: fileName is the name of the midi file to be loaded.
* Postconditions: fileName is loaded and is ready to be played.
**/
public void load(String fileName)
{
this.unload();
try
{
URL midiURL = getClass().getClassLoader().getResource(fileName);
seq = MidiSystem.getSequence(midiURL);
seqr.open();
synth.open();
// load our sequence into the sequencer.
seqr.setSequence(seq);
loaded = true;
defaultTempo = seqr.getTempoInBPM();
}
catch(IOException ioe)
{
System.out.println("MIDI error: Problem occured while reading " + midiFile.getName() + ".");
}
catch(InvalidMidiDataException imde)
{
System.out.println("MIDI error: " + midiFile.getName() + " is not a valid MIDI file or is unreadable.");
}
catch(Exception e)
{
System.out.println("MIDI error: Unexplained error occured while loading midi.");
}
}
/**
* unload()
* Unloads the current midi from the MidiPlayer and releases its resources from memory.
**/
public void unload()
{
this.stop();
seqr.close();
synth.close();
midiFile = null;
loaded = false;
}
// OTHER METHODS
/**
* setMidiID(String id)
* associates a String ID with the current midi.
* Preconditions: id is the ID we are associating with the current midi.
**/
public void setMidiID(String id)
{
midiID = id;
}
/**
* getMidiID(String id)
*
**/
public String getMidiID()
{
return new String(midiID);
}
/**
* play(boolean reset)
* plays the currently loaded midi.
* Preconditions: reset tells our midi whether or nor to begin playing from the start of the midi file's current loop start point.
* Postconditions: If reset is true, then the loaded midi begins playing from its loop start point (default 0).
* If reset is false, then the loaded midi resumes playing from its current position.
**/
public void play(boolean reset)
{
if(reset)
seqr.setTickPosition(seqr.getLoopStartPoint());
seqr.start();
}
/**
* stop()
* Pauses the current midi if it was playing.
**/
public void stop()
{
if(seqr.isOpen())
seqr.stop();
}
/**
* isRunning()
* Returns true if the current midi is playing. Returns false otherwise.
**/
public boolean isRunning()
{
return seqr.isRunning();
}
/**
* getTempo()
* Returns the current tempo of the MidiPlayer in BPM (Beats per Minute).
**/
public float getTempo()
{
return seqr.getTempoInBPM();
}
/**
* loop(int times)
* Sets the current midi to loop from start to finish a specific number of times.
* Preconditions: times is the number of times we want our midi to loop.
* Postconditions: The current midi is set to loop times times.
* If times = -1, the current midi will be set to loop infinitely.
**/
public void loop(int times)
{
loop(times,0,-1);
}
/**
* loop(int times)
* Sets the current midi to loop from a specified start point to a specified end point a specific number of times.
* Preconditions: times is the number of times we want our midi to loop.
* start is our loop's start point in ticks.
* end is our loop's end point in ticks.
* Postconditions: The current midi is set to loop from tick start to tick end times times.
* If times = -1, the current midi will be set to loop infinitely.
**/
public void loop(int times, long start, long end)
{
if(start < 0)
start = 0;
if(end > seqr.getSequence().getTickLength() || end <= 0)
end = seqr.getSequence().getTickLength();
if(start >= end && end != -1)
start = end-1;
seqr.setLoopStartPoint(start);
seqr.setLoopEndPoint(end);
if(times == -1)
seqr.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
else
seqr.setLoopCount(times);
}
/**
* resetTempo()
* Resets the MidiPlayer's tempo the the initial tempo of its current midi.
**/
public void resetTempo()
{
this.changeTempo(this.defaultTempo);
}
/**
* changeTempo(float bpm)
* Changes the MidiPlayer's current tempo.
* Preconditions: bpm is the MidiPlayer's new tempo in BPM (Beats per Minute).
* Postconditions: The MidiPlayer's current tempo is set to bpm BPM.
**/
public void changeTempo(float bpm)
{
double lengthCoeff = bpm/seqr.getTempoInBPM();
seqr.setLoopStartPoint((long) (seqr.getLoopStartPoint()*lengthCoeff));
seqr.setLoopEndPoint((long) (seqr.getLoopEndPoint()*lengthCoeff));
seqr.setTempoInBPM(bpm);
}
public void setVolume(double vol)
{
System.out.println("Midi volume change request: " + vol);
try
{
if(usingHardwareSoundbank)
{
ShortMessage volumeMessage = new ShortMessage();
for ( int i = 0; i < 16; i++ )
{
volumeMessage.setMessage( ShortMessage.CONTROL_CHANGE, i, 7, (int)(vol*127) );
receiver.send( volumeMessage, -1 );
}
}
else
{
MidiChannel[] channels = synth.getChannels();
for( int c = 0; c < channels.length; c++ )
{
if(channels[c] != null) {
channels[c].controlChange( 7, (int)( vol*127) );
}
}
}
}
catch ( Exception e )
{
e.printStackTrace();
}
}
}