package jp.sourceforge.nicoro;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.SystemClock;

import java.lang.ref.SoftReference;
import java.nio.ByteBuffer;

import static jp.sourceforge.nicoro.Log.LOG_TAG;

/**
 * 音声のストリーム再生クラス
 */
public class StreamAudioPlayer {
	private static final boolean DEBUG_LOGV = Release.IS_DEBUG && false;
	private static final boolean DEBUG_LOGD = Release.IS_DEBUG && true;
	
	/**
	 * 
	 * FFmpegの都合上、AVCODEC_MAX_AUDIO_FRAME_SIZE以上でなければならない
	 */
	private static final int AUDIO_BUFFER_BYTE_SIZE = 192000 + 0;
	
//    private static final int DUMMY_DATA_SIZE = 256 * 1024;
	
	private AudioTrackCustom mAudioTrack;
    private int mAudioSampleByteSize;
	private short[] mAudioBufferSecond;
	private int mAudioBufferSecondOffset;
	
	private SoftReference<byte[]> mRefDummyBuffer =
	    new SoftReference<byte[]>(null);
	
	private int mSampleRate;
	
	/**
	 * 二次バッファに書き込み済み音声データの合計サイズ（バイト単位）
	 */
	private long mWrittenBufferByteSize;
	/**
	 * デバイスに書き込み済み音声データの合計サイズ（バイト単位）<BR>
	 * TODO 再生位置情報としては使えない（シークで破綻するため）
	 */
	private long mWrittenDeviceByteSize;
    
//	private int mLastPlaybackHeadPosition;
	
	private int mDummyDataByteSizeAtStart;
    private long mDummyDataByteSizeAtSeek;
    
	private volatile boolean mIsFinish;
	
	private boolean mReservePause;
	
	private int mSeekLastState = 0;
	
	private int mBufferSize;
	
	private volatile boolean mIsInSeek;
	private long mSeekLastDummyStartTime;
	
	private static class AudioTrackCustom extends AudioTrack {
		public AudioTrackCustom(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) {
			super(streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, mode);
		}
		
		public int getNativeFrameCountPublic() {
			int count = getNativeFrameCount();
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, Log.buf().append("getNativeFrameCount()=")
						.append(count).toString());
			}
			return count;
		}
	}
    
    public StreamAudioPlayer() {
    	
    }
    
    /**
     * 再生の前準備
     */
    public void prepareStart() {
    	if (mAudioBufferSecond == null) {
    		mAudioBufferSecond = new short[getAudioBufferByteSize()/2*2];
    	}
    	mAudioBufferSecondOffset = 0;
    	mIsFinish = false;
    	mReservePause = false;
    }
    /**
     * 終了させる
     */
    public void finish() {
    	mIsFinish = true;
    	releaseAll();
    }
    
    /**
     * {@link android.media.AudioTrack#release()}のみ行う
     */
    public void release() {
		AudioTrack audioTrack = mAudioTrack;
		if (audioTrack != null) {
			audioTrack.release();
		}
    }
    /**
     * {@link #release()}プラスAudioTrackやバッファの開放を行う
     */
    public void releaseAll() {
    	release();
    	mAudioTrack = null;
    	mAudioBufferSecond = null;
    }

    /**
     * FFmpegによって開始
     * @param sample_rate
     * @param channels
     * @param sample_fmt
     */
    public void startByFFmpeg(int sample_rate, int channels, int sample_fmt) {
    	startCommon();
    	int sampleRateInHz = sample_rate;
    	mSampleRate = sampleRateInHz;
    	mAudioSampleByteSize = 1;
    	int channelConfig;
    	switch (channels) {
    	case 1:
    		channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
    		mAudioSampleByteSize *= 1;
    		break;
    	case 2:
			channelConfig = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
			mAudioSampleByteSize *= 2;
			break;
		default:
			channelConfig = AudioFormat.CHANNEL_CONFIGURATION_INVALID;
    		break;
    	}
    	int audioFormat;
    	switch (sample_fmt) {
    	case 0:		// SAMPLE_FMT_U8
    		audioFormat = AudioFormat.ENCODING_PCM_8BIT;
    		mAudioSampleByteSize *= 1;
    		break;
    	case 1:		// SAMPLE_FMT_S16
    		audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    		mAudioSampleByteSize *= 2;
    		break;
    	default:
    		audioFormat = AudioFormat.ENCODING_INVALID;
    		break;
    	}
    	
    	release();
    	int bufferSize = AUDIO_BUFFER_BYTE_SIZE;
        mBufferSize = bufferSize;
        mAudioTrack = createAudioTrackCustom(
        		sampleRateInHz,
        		channelConfig,
        		audioFormat,
        		bufferSize);
        mAudioTrack.play();
        
        // ダミーデータ書き込み
        writeDummyDataAtStart(bufferSize);
    }
    
    /**
     * SWF再生によって開始
     * @param sampleRateInHz
     * @param channels
     */
    public void startBySwf(int sampleRateInHz, int channels) {
    	startCommon();
    	mAudioSampleByteSize = 2;
    	mSampleRate = sampleRateInHz;
    	
    	int channelConfig;
    	switch (channels) {
    	case 1:
    		channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
    		mAudioSampleByteSize *= 1;
    		break;
    	case 2:
			channelConfig = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
			mAudioSampleByteSize *= 2;
			break;
		default:
			channelConfig = AudioFormat.CHANNEL_CONFIGURATION_INVALID;
    		break;
    	}
    	int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    	
    	release();
//    	int bufferSize = AUDIO_BUFFER_BYTE_SIZE;
    	int bufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
    	mBufferSize = bufferSize;
        mAudioTrack = createAudioTrackCustom(
        		sampleRateInHz,
        		channelConfig,
        		audioFormat,
        		bufferSize);
        mAudioTrack.play();
        
        // TODO ダミーデータ書き込み：どうも再生時間の調整の効果無し？
//        int dummyBufferSize = bufferSize;
        int dummyBufferSize = mAudioTrack.getNativeFrameCountPublic() * mAudioSampleByteSize;
//        int dummyBufferSize = DUMMY_DATA_SIZE;
//        int dummyBufferSize = 5 * sampleRateInHz * mAudioSampleByteSize;
//        int dummyBufferSize = 1 * sampleRateInHz * mAudioSampleByteSize;
//        int dummyBufferSize = AUDIO_BUFFER_BYTE_SIZE;
        writeDummyDataAtStart(dummyBufferSize);
    }
    
    private void startCommon() {
    	mWrittenBufferByteSize = 0L;
    	mWrittenDeviceByteSize = 0L;
    	mDummyDataByteSizeAtStart = 0;
    	mDummyDataByteSizeAtSeek = 0L;
    }
    
    private void writeDummyDataAtStart(int size) {
//        mDummyDataByteSizeAtStart = size;
        byte[] dummy = getDummyBuffer(size);
        final AudioTrackCustom audioTrack = mAudioTrack;
        assert audioTrack != null;
        int written = audioTrack.write(dummy, 0, size);
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, Log.buf().append("writeDummyDataAtStart() size=")
                    .append(size).append(" written=").append(written).toString());
        }
        if (written >= 0) {
            mDummyDataByteSizeAtStart = written;
        }
        audioTrack.flush();
    }
    
    public int writeDummyDataAtSeek() {
        final AudioTrackCustom audioTrack = mAudioTrack;
        if (audioTrack == null) {
            return AudioTrack.ERROR;
        }
        final int size = AUDIO_BUFFER_BYTE_SIZE;
        byte[] dummy = getDummyBuffer(size);
        int written = audioTrack.write(dummy, 0, size);
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, Log.buf().append("writeDummyDataAtSeek() size=")
                    .append(size).append(" written=").append(written).toString());
        }
        if (written >= 0) {
            mDummyDataByteSizeAtSeek += written;
        }
        audioTrack.flush();
        return written;
    }
    
    /**
     * AudioTrackを活動中にさせるため、ダミーデータを書き込み続ける
     */
    public void writeDummyDataAtSeekForAlive() {
        final AudioTrackCustom audioTrack = mAudioTrack;
        if (audioTrack == null) {
            return;
        }
        
        if (getRemainDataSizeOnDeviceMs() < 33) {
            int dummyByte = (int) (33 * mSampleRate / 1000L * mAudioSampleByteSize);
            byte[] dummy = getDummyBuffer(dummyByte);
            int written = audioTrack.write(dummy, 0, dummyByte);
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf().append("writeDummyDataAtSeekForAlive() size=")
                        .append(dummyByte).append(" written=").append(written).toString());
            }
            if (written >= 0) {
                mDummyDataByteSizeAtSeek += written;
            }
        }
    }
    
    /**
     * 冒頭に挿入したダミーデータの再生中か判定
     * @return
     */
    public boolean isInDummyDataAtStart() {
        final AudioTrackCustom audioTrack = mAudioTrack;
		if (audioTrack != null) {
//			final int dummyFrame = DUMMY_DATA_SIZE / mAudioSampleByteSize;
			final int dummyFrame = (int) (mDummyDataByteSizeAtStart / mAudioSampleByteSize);
			return (audioTrack.getPlaybackHeadPosition() < dummyFrame);
		}
		return false;
    }
    
    public boolean isInDummyDataAtSeek() {
        final AudioTrackCustom audioTrack = mAudioTrack;
        if (audioTrack != null) {
            final int dummyFrame = (int) ((mDummyDataByteSizeAtStart + mWrittenDeviceByteSize + mDummyDataByteSizeAtSeek) / mAudioSampleByteSize);
            return (audioTrack.getPlaybackHeadPosition() < dummyFrame);
        }
        return false;
    }
    
    /**
     * 再生可能状態か判定
     * @return
     */
    public boolean canPlay() {
    	return mAudioTrack != null;
    }
	
	/**
	 * 一時停止
	 */
	public void pause() {
        final AudioTrackCustom audioTrack = mAudioTrack;
		if (audioTrack != null) {
//			mLastPlaybackHeadPosition = audioTrack.getPlaybackHeadPosition();
		    if (mIsInSeek) {
		        mReservePause = true;
		    } else {
		        // TODO ただのpauseでも
		        // I/AudioHardwareQSD: AudioHardware pcm playback is going to standby.
		        // が発生する場合が
		        audioTrack.pause();
		    }
		}
    }
	/**
	 * 一時停止を解除
	 */
	public void restart() {
        final AudioTrackCustom audioTrack = mAudioTrack;
		if (audioTrack != null) {
            if (mIsInSeek) {
                // TODO これでいいか？
                mSeekLastState = AudioTrack.PLAYSTATE_PLAYING;
                audioTrack.play();
            } else {
                audioTrack.play();
            }
//		    audioTrack.setPlaybackHeadPosition(mLastPlaybackHeadPosition);
		}
		mReservePause = false;
    }
	/**
	 * ダミーデータを過ぎて、本当の音声部分が再生開始するまで待つ
	 */
	public void waitRealStartAtStart() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, "StreamAudioPlayer#waitRealStartAtStart()");
        }
	    AudioTrackCustom audioTrack = null;
		while (!mIsFinish) {
	        audioTrack = mAudioTrack;
			if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
				break;
			}
			
			// イベントはないのでポーリングで待つ
			SystemClock.sleep(10L);
		}

		assert mAudioSampleByteSize > 0;
//        final int dummyFrame = 1;
//        final int dummyFrame = DUMMY_DATA_SIZE / mAudioSampleByteSize;
        final int dummyFrame = (int) (mDummyDataByteSizeAtStart / mAudioSampleByteSize);
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, Log.buf().append("dummyFrame=")
                    .append(dummyFrame).toString());
        }
		while (!mIsFinish) {
			// TODO: とりあえずAudioTrack開始までポーリング
		    // OnPlaybackPositionUpdateListenerの精度は微妙？
			assert audioTrack != null;
			int playbackHeadPosition = audioTrack.getPlaybackHeadPosition();
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, Log.buf().append("getPlaybackHeadPosition=")
						.append(playbackHeadPosition).toString());
			}
			int diffFrame = dummyFrame - playbackHeadPosition;
			if (diffFrame <= 0) {
				break;
			}
			// sleepの誤差を考慮して待つ
			// TODO 待ち時間長いとスレッド終了チェックに間があく
			long waitTime = diffFrame * 1000L / mSampleRate;
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf().append("waitTime=")
                        .append(waitTime).toString());
            }
			if (waitTime > 10) {
	            SystemClock.sleep(waitTime - 10L);
			} else {
			    SystemClock.sleep(1L);
			}
		}
		
		if (mReservePause) {
			pause();
			mReservePause = false;
		}
    }

	/**
	 * 再生が終了／完了するまで待つ
	 */
	public void waitPlayEnd() {
		while (!mIsFinish) {
			final AudioTrack audioTrack = mAudioTrack;
			if (audioTrack == null) {
				break;
			}
			if (audioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) {
				break;
			}
			SystemClock.sleep(33L);
		}
    }
	
	/**
	 * 音声バッファのサイズをバイト単位で算出
	 * @return
	 */
	public int getAudioBufferByteSize() {
//			int audioBufferByteSize = AUDIO_BUFFER_BYTE_SIZE;
		int audioBufferByteSize = mAudioTrack.getSampleRate() * mAudioSampleByteSize;
		if (audioBufferByteSize < AUDIO_BUFFER_BYTE_SIZE) {
			audioBufferByteSize = AUDIO_BUFFER_BYTE_SIZE;
		}
		return audioBufferByteSize;
    }
	
	/**
     * 音声バッファに格納可能な残りサイズをバイト単位で算出
	 * @return
	 */
	public int getRemainBufferByteSize() {
        final short[] audioBufferSecond = mAudioBufferSecond;
		if (audioBufferSecond == null) {
			// 終了済み
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, Log.buf()
						.append("getRemainBufferByteSize(): mAudioBufferSecond is null alreday")
						.toString());
			}
			return 0;
		}
		return (audioBufferSecond.length - mAudioBufferSecondOffset) * 2;
	}
	
	/**
	 * 音声データをバッファに書き込む
	 * @param buffer 音声データ
	 * @param sizeInShorts 音声データのサイズ（short単位）
	 */
	public void writeBuffer(short[] buffer, int sizeInShorts) {
		assert sizeInShorts >= 0;
		assert sizeInShorts <= buffer.length;
        final short[] audioBufferSecond = mAudioBufferSecond;
		if (audioBufferSecond == null) {
			// 終了済み
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, Log.buf()
						.append("writeBuffer(): mAudioBufferSecond is null alreday")
						.toString());
			}
			return;
		}
		if (audioBufferSecond.length - mAudioBufferSecondOffset < sizeInShorts) {
			// TODO 空きが足らないのでデバイスに流す→一時停止でデバイスは止まっているのにデコーダが動き続けるのが悪い？
			flushBufferToAudioTrack();
		}
		System.arraycopy(buffer, 0, audioBufferSecond, mAudioBufferSecondOffset, sizeInShorts);
		mAudioBufferSecondOffset += sizeInShorts;
		mWrittenBufferByteSize += sizeInShorts * 2;
	}
    public void writeBuffer(ByteBuffer buffer, int sizeInShorts) {
        assert sizeInShorts >= 0;
        assert sizeInShorts * 2 <= buffer.capacity();
        final short[] audioBufferSecond = mAudioBufferSecond;
        if (audioBufferSecond == null) {
            // 終了済み
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf()
                        .append("writeBuffer(): mAudioBufferSecond is null alreday")
                        .toString());
            }
            return;
        }
        if (audioBufferSecond.length - mAudioBufferSecondOffset < sizeInShorts) {
            // TODO 空きが足らないのでデバイスに流す→一時停止でデバイスは止まっているのにデコーダが動き続けるのが悪い？
            flushBufferToAudioTrack();
        }
        FFmpegVideoDecoder.copyDirect(buffer, 0,
                audioBufferSecond, mAudioBufferSecondOffset, sizeInShorts * 2);
        mAudioBufferSecondOffset += sizeInShorts;
        mWrittenBufferByteSize += sizeInShorts * 2;
    }
	
	public void writeBufferOnlyPool(short[] buffer, int sizeInShorts) {
        assert sizeInShorts >= 0;
        assert sizeInShorts <= buffer.length;
        final short[] audioBufferSecond = mAudioBufferSecond;
        if (audioBufferSecond == null) {
            // 終了済み
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf()
                        .append("writeBufferOnlyPool(): mAudioBufferSecond is null alreday")
                        .toString());
            }
            return;
        }
        if (audioBufferSecond.length - mAudioBufferSecondOffset < sizeInShorts) {
            // 空きが足らないときは先頭削る
            // TODO 処理無駄に重いかも
            int removeSize = sizeInShorts - (audioBufferSecond.length - mAudioBufferSecondOffset);
            System.arraycopy(audioBufferSecond, removeSize, audioBufferSecond, 0, audioBufferSecond.length - removeSize);
            mAudioBufferSecondOffset = audioBufferSecond.length - removeSize;
        }
        System.arraycopy(buffer, 0, audioBufferSecond, mAudioBufferSecondOffset, sizeInShorts);
        mAudioBufferSecondOffset += sizeInShorts;
        mWrittenBufferByteSize += sizeInShorts * 2;
    }
	
	/**
	 * 音声バッファを{@link android.media.AudioTrack}に転送
	 */
	public void flushBufferToAudioTrack() {
	    final short[] audioBufferSecond = mAudioBufferSecond;
        final AudioTrackCustom audioTrack = mAudioTrack;
		if (audioBufferSecond == null || audioTrack == null) {
			// 終了済み
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, Log.buf()
						.append("flushBufferToAudioTrack(): already null : mAudioBufferSecond=")
						.append(audioBufferSecond).append(" mAudioTrack=")
						.append(audioTrack)
						.toString());
			}
			return;
		}
    	int writtenSize = audioTrack.write(audioBufferSecond, 0, mAudioBufferSecondOffset);
    	if (writtenSize < 0) {
    		Log.d(LOG_TAG, Log.buf().append("AudioTrack write NG: ")
    				.append(writtenSize).toString());
    		return;
    	}
    	if (DEBUG_LOGV) {
    		Log.v(LOG_TAG, Log.buf().append("writtenSize=")
    				.append(writtenSize).toString());
    	}
		mWrittenDeviceByteSize += writtenSize * 2;
    	if (writtenSize == mAudioBufferSecondOffset) {
    		mAudioBufferSecondOffset = 0;
    	} else {
    		assert writtenSize < mAudioBufferSecondOffset;
    		System.arraycopy(audioBufferSecond, writtenSize,
    		        audioBufferSecond, 0,
    				mAudioBufferSecondOffset - writtenSize);
    		mAudioBufferSecondOffset -= writtenSize;
    	}
	}
	public void flushAudioTrack() {
        final AudioTrackCustom audioTrack = mAudioTrack;
        assert audioTrack != null;
        if (audioTrack != null) {
            audioTrack.flush();
        }
    }
	
	/**
	 * キャッシュ済みの音声サイズをshort単位で取得
	 * @return
	 */
	public int getCachedSize() {
		return mAudioBufferSecondOffset;
	}
	/**
	 * 音声バッファに十分なキャッシュがあるか判定
	 * @return
	 */
	public boolean hasEnoughCache() {
        final short[] audioBufferSecond = mAudioBufferSecond;
        final AudioTrackCustom audioTrack = mAudioTrack;
		if (audioBufferSecond == null || audioTrack == null) {
			// 終了済み
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, Log.buf()
						.append("hasEnoughCache(): already null : mAudioBufferSecond=")
                        .append(audioBufferSecond).append(" mAudioTrack=")
                        .append(audioTrack)
						.toString());
			}
			return false;
		}
		int enough = audioTrack.getSampleRate() * mAudioSampleByteSize * 25 / 100;
		if (enough > audioBufferSecond.length * 1 / 2) {
			enough = audioBufferSecond.length * 1 / 2;
		}
		return mAudioBufferSecondOffset > enough;
	}
	public int getAudioSampleByteSize() {
		return mAudioSampleByteSize;
	}
	public int getSampleRate() {
        final AudioTrackCustom audioTrack = mAudioTrack;
		if (audioTrack != null) {
			return audioTrack.getSampleRate();
		} else {
			return 0;
		}
	}
	
	/**
	 * 現在の再生位置を{@link Rational}で取得<br>
	 * TODO 現状シークすると破綻する
	 * @param rational
	 */
	public void getCurrentPosition(Rational rational) {
        // TODO シーク発生時の計算方法どうするか
        final AudioTrackCustom audioTrack = mAudioTrack;
		if (audioTrack != null) {
			final int dummyFrame = (int) ((mDummyDataByteSizeAtStart + mDummyDataByteSizeAtSeek) / mAudioSampleByteSize);
			int pos = (audioTrack.getPlaybackHeadPosition() - dummyFrame);
			if (pos >= 0) {
				rational.num = pos;
			} else {
				rational.num = 0;
			}
			rational.den = audioTrack.getSampleRate();
		} else {
			rational.num = 0;
			rational.den = 1;
		}
	}
	
	/**
	 * デバイス上に再生待ちで残っている音声データのサイズを取得
	 * @return
	 */
	public int getRemainDataSizeOnDeviceMs() {
	    // TODO シーク発生時の計算方法どうするか
	    final AudioTrackCustom audioTrack = mAudioTrack;
		if (audioTrack != null) {
			final int dummyFrame = (int) ((mDummyDataByteSizeAtStart + mDummyDataByteSizeAtSeek) / mAudioSampleByteSize);
			int currentFrame = (audioTrack.getPlaybackHeadPosition() - dummyFrame);
			int remainFrame = ((int) (mWrittenDeviceByteSize / mAudioSampleByteSize)) - currentFrame;
			assert remainFrame >= 0;
			return (int) ((long) remainFrame * 1000L / audioTrack.getSampleRate());
		} else {
			return 0;
		}
	}
	
//	/**
//	 * 音声バッファと{@link android.media.AudioTrack}のクリア
//	 */
//	public void clearBuffer() {
//    	mAudioBufferSecondOffset = 0;
//        final AudioTrackCustom audioTrack = mAudioTrack;
//		if (audioTrack != null) {
//	    	final int lastState = audioTrack.getPlayState();
//	    	audioTrack.stop();
////	    	assert audioTrack.getPlaybackHeadPosition() == 0;
//	    	audioTrack.play();
//	    	if (lastState == AudioTrack.PLAYSTATE_PAUSED) {
//	    	    audioTrack.pause();
//	    	}
//		}
//	}
    
    public void startSeek() {
        mIsInSeek = true;
        mSeekLastDummyStartTime = SystemClock.uptimeMillis();
        mAudioBufferSecondOffset = 0;
        AudioTrackCustom audioTrack = mAudioTrack;
        if (audioTrack != null) {
            mSeekLastState = audioTrack.getPlayState();

            float minVolume = AudioTrack.getMinVolume();
            audioTrack.setStereoVolume(minVolume, minVolume);
            if (mSeekLastState == AudioTrack.PLAYSTATE_PAUSED) {
                // 完全停止を避けるため一時停止中でも動作させる
                audioTrack.play();
            }
        }
    }
    
    public void endSeek(int currentSecond) {
        final AudioTrackCustom audioTrack = mAudioTrack;
        if (audioTrack != null) {
            if (mSeekLastState == AudioTrack.PLAYSTATE_PAUSED) {
                audioTrack.pause();
            }
            if (mReservePause) {
                audioTrack.pause();
                mReservePause = false;
            }
            float maxVolume = AudioTrack.getMaxVolume();
            audioTrack.setStereoVolume(maxVolume, maxVolume);
        }
        mIsInSeek = false;
    }
	
	/**
	 * ダミーデータ再生中に一時停止を予約
	 */
	public void reservePause() {
		mReservePause = true;
	}
	
	private byte[] getDummyBuffer(int size) {
	    byte[] buf = mRefDummyBuffer.get();
	    if (buf == null || buf.length < size) {
	        buf = new byte[size];
	        mRefDummyBuffer = new SoftReference<byte[]>(buf);
	    }
	    return buf;
	}

    private AudioTrackCustom createAudioTrackCustom(int sampleRateInHz,
            int channelConfig, int audioFormat, int bufferSize) {
        for (int i = 0; i < 3; ++i) {
            AudioTrackCustom audioTrack = new AudioTrackCustom(
                    AudioManager.STREAM_MUSIC,
                    sampleRateInHz,
                    channelConfig,
                    audioFormat,
                    bufferSize,
                    AudioTrack.MODE_STREAM);
            int state = audioTrack.getState();
            if (state != AudioTrack.STATE_UNINITIALIZED) {
                assert state == AudioTrack.STATE_INITIALIZED;
                return audioTrack;
            }
            audioTrack = null;
            System.gc();
            System.gc();
            SystemClock.sleep(100L);
        }
        throw new IllegalStateException("Create AudioTrack failed");
    }
}
