1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.jme3.audio;
34
35import com.jme3.util.NativeObject;
36import java.io.Closeable;
37import java.io.IOException;
38import java.io.InputStream;
39import java.util.logging.Level;
40import java.util.logging.Logger;
41
42/**
43 * <code>AudioStream</code> is an implementation of AudioData that
44 * acquires the audio from an InputStream. Audio can be streamed
45 * from network, hard drive etc. It is assumed the data coming
46 * from the input stream is uncompressed.
47 *
48 * @author Kirill Vainer
49 */
50public class AudioStream extends AudioData implements Closeable{
51
52    private final static Logger logger = Logger.getLogger(AudioStream.class.getName());
53    protected InputStream in;
54    protected float duration = -1f;
55    protected boolean open = false;
56    protected int[] ids;
57
58    public AudioStream(){
59        super();
60    }
61
62    protected AudioStream(int[] ids){
63        // Pass some dummy ID so handle
64        // doesn't get created.
65        super(-1);
66        // This is what gets destroyed in reality
67        this.ids = ids;
68    }
69
70    public void updateData(InputStream in, float duration){
71        if (id != -1 || this.in != null)
72            throw new IllegalStateException("Data already set!");
73
74        this.in = in;
75        this.duration = duration;
76        open = true;
77    }
78
79    /**
80     * Reads samples from the stream. The format of the data
81     * depends on the getSampleRate(), getChannels(), getBitsPerSample()
82     * values.
83     *
84     * @param buf Buffer where to read the samples
85     * @param offset The offset in the buffer where to read samples
86     * @param length The length inside the buffer where to read samples
87     * @return number of bytes read.
88     */
89    public int readSamples(byte[] buf, int offset, int length){
90        if (!open)
91            return -1;
92
93        try{
94            return in.read(buf, offset, length);
95        }catch (IOException ex){
96            return -1;
97        }
98    }
99
100    /**
101     * Reads samples from the stream.
102     *
103     * @see AudioStream#readSamples(byte[], int, int)
104     * @param buf Buffer where to read the samples
105     * @return number of bytes read.
106     */
107    public int readSamples(byte[] buf){
108        return readSamples(buf, 0, buf.length);
109    }
110
111    public float getDuration(){
112        return duration;
113    }
114
115    @Override
116    public int getId(){
117        throw new RuntimeException("Don't use getId() on streams");
118    }
119
120    @Override
121    public void setId(int id){
122        throw new RuntimeException("Don't use setId() on streams");
123    }
124
125    public void initIds(int count){
126        ids = new int[count];
127    }
128
129    public int getId(int index){
130        return ids[index];
131    }
132
133    public void setId(int index, int id){
134        ids[index] = id;
135    }
136
137    public int[] getIds(){
138        return ids;
139    }
140
141    public void setIds(int[] ids){
142        this.ids = ids;
143    }
144
145    @Override
146    public DataType getDataType() {
147        return DataType.Stream;
148    }
149
150    @Override
151    public void resetObject() {
152        id = -1;
153        ids = null;
154        setUpdateNeeded();
155    }
156
157    @Override
158    public void deleteObject(Object rendererObject) {
159        // It seems that the audio renderer is already doing a good
160        // job at deleting audio streams when they finish playing.
161//        ((AudioRenderer)rendererObject).deleteAudioData(this);
162    }
163
164    @Override
165    public NativeObject createDestructableClone() {
166        return new AudioStream(ids);
167    }
168
169    /**
170     * @return Whether the stream is open or not. Reading from a closed
171     * stream will always return eof.
172     */
173    public boolean isOpen(){
174        return open;
175    }
176
177    /**
178     * Closes the stream, releasing all data relating to it. Reading
179     * from the stream will return eof.
180     * @throws IOException
181     */
182    public void close() {
183        if (in != null && open){
184            try{
185                in.close();
186            }catch (IOException ex){
187            }
188            open = false;
189        }else{
190            throw new RuntimeException("AudioStream is already closed!");
191        }
192    }
193
194
195    public void setTime(float time){
196        if(in instanceof SeekableStream){
197            ((SeekableStream)in).setTime(time);
198        }else{
199            logger.log(Level.WARNING,"Cannot use setTime on a stream that is not seekable. You must load the file with the streamCache option set to true");
200        }
201    }
202
203
204}
205