1 package org.musicontroller.streaming;
2
3 import java.io.File;
4 import java.io.FileInputStream;
5 import java.io.FileNotFoundException;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.io.OutputStream;
9 import java.net.SocketException;
10 import java.util.Map;
11
12 import javax.servlet.http.HttpServletRequest;
13
14 import org.apache.log4j.Logger;
15 import org.apache.tapestry.IRequestCycle;
16 import org.apache.tapestry.engine.IEngineService;
17 import org.apache.tapestry.engine.ILink;
18 import org.apache.tapestry.services.LinkFactory;
19 import org.apache.tapestry.services.ServiceConstants;
20 import org.apache.tapestry.util.ContentType;
21 import org.apache.tapestry.web.WebResponse;
22 import org.hibernate.HibernateException;
23 import org.hibernate.Session;
24 import org.hibernate.SessionFactory;
25 import org.hibernate.Transaction;
26 import org.musicontroller.DJ;
27 import org.musicontroller.MusiControllerException;
28 import org.musicontroller.core.Song;
29 import org.musicontroller.security.IUser;
30 import org.springframework.orm.hibernate3.SessionHolder;
31 import org.springframework.transaction.support.TransactionSynchronizationManager;
32
33 public class StreamService implements IEngineService {
34 private static final Logger log = Logger.getLogger(StreamService.class);
35
36 HttpServletRequest _req = null;
37 public HttpServletRequest getServletRequest() {
38 return _req;
39 }
40 public void setServletRequest(HttpServletRequest req) {
41 _req = req;
42 }
43
44 public static final String SERVICE_NAME = "stream";
45
46
47
48
49
50
51 private static final int BUFFER_SIZE = 16384;
52
53 private LinkFactory _linkFactory;
54 private WebResponse _response;
55
56 @SuppressWarnings("unchecked")
57 public ILink getLink(boolean post, Object parameter) {
58 Map<String,String> parameters = (Map<String,String>) parameter;
59 parameters.put(ServiceConstants.SERVICE, getName());
60
61 return _linkFactory.constructLink(this, false, parameters, true);
62 }
63
64
65
66
67
68
69
70
71
72
73
74 public void service(IRequestCycle cycle) throws IOException {
75 boolean outputMetaData = "1".equals(cycle.getInfrastructure().getRequest().getHeader("Icy-MetaData"));
76 long userid = Long.parseLong(cycle.getParameter("userid"));
77 String passhash = cycle.getParameter("passhash");
78
79 final DJ dj = StreamMaster.getDJByUser(userid);
80 if (dj==null) {
81
82 log.error("User-ID: "+userid+" has not logged on, ignoring stream-request");
83 return;
84 }
85
86 IUser user = dj.getUser();
87 if (passhash==null || !passhash.equals(user.getPassword())) {
88 log.debug("False credentials provided, ignoring stream-request.");
89 } else {
90 if (log.isDebugEnabled()) {
91 log.debug("Stream started for: "+getServletRequest().getRemoteAddr());
92 log.debug("Metadata requested: "+ (outputMetaData ? "yes" : "no"));
93 log.debug("User ID: "+userid);
94 log.debug("Metadata interval and buffer size: "+BUFFER_SIZE);
95 }
96
97 Song song = null;
98
99 try {
100 ShoutcastOutputStream sout = new ShoutcastOutputStream(
101 initOutputStream(outputMetaData),
102 BUFFER_SIZE,
103 outputMetaData
104 );
105 MpegOutputStream out = new MpegOutputStream(sout);
106
107 boolean doStream = true;
108 while (doStream) {
109 song = chooseSong(dj);
110 if (song!=null) {
111 log.debug("Selected new song for User ID: "+userid);
112 try {
113 dj.setPlaying(true);
114 long startTime = System.currentTimeMillis();
115
116 sout.setMetadata(song.getBand().getName() + " - " + song.getName(),"");
117 streamSong(out,song,dj);
118
119 long endTime = System.currentTimeMillis();
120 long songDuration = endTime - startTime;
121 if(song.getLength()>songDuration*2) {
122 log.error("The song played in less than half its real length. Synchronization lost.");
123 } else {
124 dj.confirmPlay();
125 }
126 } catch (SkipException ske) {
127 dj.confirmSkip();
128 } catch (MusiControllerException mce) {
129
130
131
132
133 log.error(mce.getMessage());
134 } catch (SocketException se) {
135 doStream = false;
136
137 }
138 }
139 }
140 } catch (Exception e) {
141 log.error(e);
142 }
143
144 log.debug("Stream ended for User ID: "+userid);
145 dj.setPlaying(false);
146 }
147 }
148
149
150
151
152
153
154 private Song chooseSong(final DJ dj) {
155 Song song = null;
156 SessionFactory factory = dj.getMusiController().getDao().getSessionFactory2();
157 Session session = factory.openSession();
158 TransactionSynchronizationManager.bindResource(factory, new SessionHolder(session));
159 Transaction tx = session.beginTransaction();
160 try {
161 song=dj.choose();
162 File tmp = song.getLink().getFile();
163 log.debug(tmp.getAbsolutePath());
164 tx.commit();
165 } catch (HibernateException e) {
166 log.error(e.getMessage());
167 tx.rollback();
168 } finally {
169 session.close();
170 TransactionSynchronizationManager.unbindResource(factory);
171 }
172 return song;
173 }
174
175 private OutputStream initOutputStream(boolean metadata) throws IOException {
176 OutputStream out = _response.getOutputStream(new ContentType("audio/mpeg"));
177
178
179 _response.setHeader("icy-notice1","<BR>This stream requires <a href=\"http://www.winamp.com/\">Winamp</a><BR>");
180 _response.setHeader("icy-notice1","MusiController SHOUTcast-implementation<BR>");
181 _response.setHeader("icy-name","MusiController");
182 _response.setHeader("icy-genre","All sorts");
183 _response.setHeader("icy-url","http://musicontroller.sourceforge.net");
184 _response.setHeader("icy-pub","0");
185 if (metadata) _response.setIntHeader("icy-metaint",BUFFER_SIZE);
186 _response.setHeader("icy-br","192");
187
188 log.debug("Outputstream initialized");
189
190 return out;
191 }
192
193 private void streamSong(MpegOutputStream out, Song song, final DJ dj) throws IOException, SkipException, MusiControllerException {
194 InputStream in = null;
195 File songfile = song.getLink().getFile();
196 try {
197 in = new FileInputStream(songfile);
198 out.reset();
199 copy(in,out,
200 new IStreamController() {
201 public boolean mustSkip() {
202 return dj.mustSkip();
203 }
204
205 public void setPlayingTime(int millis) {
206 dj.setPlayingTime(millis);
207 }
208 }
209 );
210 } catch (FileNotFoundException e) {
211
212 String s = "File not found! "+songfile.getAbsolutePath();
213 log.fatal(s);
214 throw new MusiControllerException(s);
215 } finally {
216 if (in!=null) {
217 in.close();
218 }
219 }
220 }
221
222 public String getName() {
223 return SERVICE_NAME;
224 }
225
226 public void setLinkFactory(LinkFactory linkFactory) {
227 _linkFactory = linkFactory;
228 }
229
230 public void setResponse(WebResponse response) {
231 _response = response;
232 }
233
234
235
236
237
238
239
240
241
242 public void copy(InputStream in, MpegOutputStream out, IStreamController controller) throws IOException, SkipException {
243 log.debug("Streaming from inputstream...");
244
245 int read;
246 byte[] buf = new byte[16384];
247
248 while ((read=in.read(buf,0,buf.length))!=-1) {
249 out.write(buf,0,read);
250 if (controller.mustSkip()) {
251 log.debug("Skiprequest received");
252 throw new SkipException();
253 }
254 controller.setPlayingTime(out.getStreamedLengthInMillis());
255 }
256 log.debug("Streaming from inputstream done.");
257 }
258
259 }