/** * Created : Mar 25, 2012 * * @author pquiring */ import java.io.*; import java.util.*; import java.awt.event.*; import javax.swing.*; import javax.swing.tree.*; import javax.swing.table.*; import javaforce.*; import javaforce.jbus.*; import javaforce.jni.*; import javaforce.media.*; public class MainPanel extends javax.swing.JPanel implements ActionListener { /** * Creates new form MainPanel */ public MainPanel() { initComponents(); playIcon = new javax.swing.ImageIcon(getClass().getResource("/play.png")); pauseIcon = new javax.swing.ImageIcon(getClass().getResource("/pause.png")); model = (DefaultTableModel)table.getModel(); if (!JF.isWindows()) { jbusClient = new JBusClient(null, null); //send only jbusClient.start(); } loadIcons(); xml.root.setName("jMedia"); library = xml.addTag(xml.root, "Library", "", ""); music = xml.addTag(library, "Music", "", ""); music.isLeaf = true; //avoid showing files in JTree video = xml.addTag(library, "Video", "", ""); video.isLeaf = true; //avoid showing files in JTree // playlists = xml.addTag(xml.root, "Play Lists", "", ""); if (!JF.isWindows()) { media = xml.addTag(xml.root, "Media", "", ""); } addLibrary(new File(JF.getUserPath() + "/Music")); addLibrary(new File(JF.getUserPath() + "/Videos")); showAll(); This = this; } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { jSplitPane1 = new javax.swing.JSplitPane(); jScrollPane1 = new javax.swing.JScrollPane(); tree = new javax.swing.JTree(); jScrollPane2 = new javax.swing.JScrollPane(); table = new javax.swing.JTable(); jToolBar1 = new javax.swing.JToolBar(); play = new javax.swing.JButton(); prev = new javax.swing.JButton(); next = new javax.swing.JButton(); jSeparator1 = new javax.swing.JToolBar.Separator(); repeat = new javax.swing.JToggleButton(); random = new javax.swing.JToggleButton(); jSeparator2 = new javax.swing.JToolBar.Separator(); extract = new javax.swing.JButton(); jSeparator3 = new javax.swing.JToolBar.Separator(); addFolder = new javax.swing.JButton(); time = new javax.swing.JSlider(); jSplitPane1.setResizeWeight(0.5); tree.setModel(xml.getTreeModel()); tree.addTreeSelectionListener(new javax.swing.event.TreeSelectionListener() { public void valueChanged(javax.swing.event.TreeSelectionEvent evt) { treeValueChanged(evt); } }); jScrollPane1.setViewportView(tree); jSplitPane1.setLeftComponent(jScrollPane1); table.setModel(new javax.swing.table.DefaultTableModel( new Object [][] { }, new String [] { "Status", "Select", "Track #", "Track Name", "Artist", "Album", "Length" } ) { Class[] types = new Class [] { java.lang.Object.class, java.lang.Boolean.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class }; boolean[] canEdit = new boolean [] { false, true, false, true, true, true, false }; public Class getColumnClass(int columnIndex) { return types [columnIndex]; } public boolean isCellEditable(int rowIndex, int columnIndex) { return canEdit [columnIndex]; } }); table.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); table.setShowVerticalLines(false); table.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent evt) { tableMouseClicked(evt); } }); jScrollPane2.setViewportView(table); if (table.getColumnModel().getColumnCount() > 0) { table.getColumnModel().getColumn(0).setPreferredWidth(20); table.getColumnModel().getColumn(0).setCellRenderer(statusCellRenderer); table.getColumnModel().getColumn(1).setPreferredWidth(20); table.getColumnModel().getColumn(2).setPreferredWidth(20); } jSplitPane1.setRightComponent(jScrollPane2); jToolBar1.setFloatable(false); jToolBar1.setRollover(true); play.setIcon(new javax.swing.ImageIcon(getClass().getResource("/play.png"))); // NOI18N play.setFocusable(false); play.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); play.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); play.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { playActionPerformed(evt); } }); jToolBar1.add(play); prev.setIcon(new javax.swing.ImageIcon(getClass().getResource("/prev.png"))); // NOI18N prev.setFocusable(false); prev.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); prev.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); prev.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { prevActionPerformed(evt); } }); jToolBar1.add(prev); next.setIcon(new javax.swing.ImageIcon(getClass().getResource("/next.png"))); // NOI18N next.setFocusable(false); next.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); next.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); next.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { nextActionPerformed(evt); } }); jToolBar1.add(next); jToolBar1.add(jSeparator1); repeat.setIcon(new javax.swing.ImageIcon(getClass().getResource("/repeat.png"))); // NOI18N repeat.setFocusable(false); repeat.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); repeat.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); jToolBar1.add(repeat); random.setIcon(new javax.swing.ImageIcon(getClass().getResource("/random.png"))); // NOI18N random.setFocusable(false); random.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); random.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); jToolBar1.add(random); jSeparator2.setEnabled(false); jToolBar1.add(jSeparator2); extract.setText("Extract"); extract.setEnabled(false); extract.setFocusable(false); extract.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); extract.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); extract.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { extractActionPerformed(evt); } }); jToolBar1.add(extract); jToolBar1.add(jSeparator3); addFolder.setText("Add Folder"); addFolder.setFocusable(false); addFolder.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); addFolder.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); addFolder.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { addFolderActionPerformed(evt); } }); jToolBar1.add(addFolder); time.setValue(0); time.addChangeListener(new javax.swing.event.ChangeListener() { public void stateChanged(javax.swing.event.ChangeEvent evt) { timeStateChanged(evt); } }); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jSplitPane1) .addComponent(jToolBar1, javax.swing.GroupLayout.DEFAULT_SIZE, 565, Short.MAX_VALUE) .addComponent(time, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(jToolBar1, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(time, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jSplitPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 657, Short.MAX_VALUE)) ); }// </editor-fold>//GEN-END:initComponents private void treeValueChanged(javax.swing.event.TreeSelectionEvent evt) {//GEN-FIRST:event_treeValueChanged if (ripping) return; extract.setEnabled(false); int cnt = tree.getSelectionCount(); if (cnt != 1) return; XML.XMLTag tag = xml.getTag(tree.getSelectionPath()); if (tag == xml.root) return; if (media != null && tag == media) { listDiscs(); } if (media != null && tag.getParent() == media) { currentIdx = -1; //BUG : status will no longer get updated listDisc(tag); } else { if (tag.getParent() == xml.root) return; //library or playlists currentIdx = -1; //BUG : status will no longer get updated listTag(tag); } }//GEN-LAST:event_treeValueChanged private void tableMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_tableMouseClicked updateToolbar(); if (evt.getClickCount() != 2) return; playActionPerformed(null); }//GEN-LAST:event_tableMouseClicked private void playActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playActionPerformed int idx = table.getSelectedRow(); if ((playing) && (idx == currentIdx)) { if (paused) resume(); else pause(); return; } if (playing) stop(true); if (random.isSelected()) { makeRandomIdxList(); randomIdx = 0; if (randomIdx >= randomIdxList.length) return; idx = randomIdxList[randomIdx]; } else { idx = table.getSelectedRow(); if (idx == -1) return; } play(idx); }//GEN-LAST:event_playActionPerformed private void timeStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_timeStateChanged seek(); }//GEN-LAST:event_timeStateChanged private void extractActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_extractActionPerformed extract(); }//GEN-LAST:event_extractActionPerformed private void nextActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextActionPerformed next(); }//GEN-LAST:event_nextActionPerformed private void prevActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevActionPerformed prev(); }//GEN-LAST:event_prevActionPerformed private void addFolderActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addFolderActionPerformed addFolder(); }//GEN-LAST:event_addFolderActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton addFolder; private javax.swing.JButton extract; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JScrollPane jScrollPane2; private javax.swing.JToolBar.Separator jSeparator1; private javax.swing.JToolBar.Separator jSeparator2; private javax.swing.JToolBar.Separator jSeparator3; private javax.swing.JSplitPane jSplitPane1; private javax.swing.JToolBar jToolBar1; private javax.swing.JButton next; private javax.swing.JButton play; private javax.swing.JButton prev; private javax.swing.JToggleButton random; private javax.swing.JToggleButton repeat; private javax.swing.JTable table; private javax.swing.JSlider time; private javax.swing.JTree tree; // End of variables declaration//GEN-END:variables private XML xml = new XML(); private XML.XMLTag library, playlists, media; private XML.XMLTag music, video; //library sub-tags private ArrayList<String> tableFiles = new ArrayList<String>(); private MediaDecoder decoder; //ffmpeg decoder private long frameCount; private long audioCount; private final Object countLock = new Object(); private boolean playing, paused, updatingPos, eof, preBuffering; private int currentIdx = -1; private VideoPanel videoPanel; private javax.swing.Timer timer; //to update position private DefaultTableModel model; private boolean ripping, abort; private JFImage icon_playing, icon_paused, icon_ripped, icon_ripping; private int randomIdx, randomIdxList[]; private StatusCellRenderer statusCellRenderer = new StatusCellRenderer(); private JBusClient jbusClient; private ReadThread readThread; private Thread playThread; private static MainPanel This; private long fileLength; private int seekPosition; private long mediaLength; private Icon playIcon, pauseIcon; private class StatusCellRenderer extends javax.swing.table.DefaultTableCellRenderer { protected void setValue(Object value) { JFImage image = (JFImage)value; //NOTE:may be null this.setIcon(image); this.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); } } private void loadIcons() { icon_playing = new JFImage(); icon_playing.loadPNG(this.getClass().getClassLoader().getResourceAsStream("playing.png")); icon_paused = new JFImage(); icon_paused.loadPNG(this.getClass().getClassLoader().getResourceAsStream("paused.png")); icon_ripped = new JFImage(); icon_ripped.loadPNG(this.getClass().getClassLoader().getResourceAsStream("ripped.png")); icon_ripping = new JFImage(); icon_ripping.loadPNG(this.getClass().getClassLoader().getResourceAsStream("ripping.png")); } private void makeRandomIdxList() { int length = table.getRowCount(); randomIdxList = new int[length]; boolean used[] = new boolean[length]; Random r = new Random(); for(int a=0;a<length;a++) { int idx = r.nextInt() % length; while (used[idx]) { idx++; if (idx == length) idx = 0; } used[idx] = true; randomIdxList[a] = idx; } } private boolean isMediaMusic(String fn) { if (fn.endsWith(".wav")) return true; if (fn.endsWith(".fla")) return true; if (fn.endsWith(".mp3")) return true; if (fn.endsWith(".wma")) return true; if (fn.endsWith(".oga")) return true; if (fn.endsWith(".spx")) return true; return false; } private boolean isMediaVideo(String fn) { if (fn.endsWith(".ogg")) return true; if (fn.endsWith(".ogv")) return true; if (fn.endsWith(".avi")) return true; if (fn.endsWith(".wmv")) return true; if (fn.endsWith(".mpg")) return true; if (fn.endsWith(".mp4")) return true; if (fn.endsWith(".flv")) return true; if (fn.endsWith(".mpeg")) return true; if (fn.endsWith(".3gp")) return true; if (fn.endsWith(".h263")) return true; if (fn.endsWith(".h264")) return true; if (fn.endsWith(".webm")) return true; if (fn.endsWith(".mov")) return true; return false; } private void addLibrary(File file) { File files[] = file.listFiles(); if (files == null) return; for(int a=0;a<files.length;a++) { if (files[a].isDirectory()) { addLibrary(files[a]); } else { if (isMediaMusic(files[a].getName().toLowerCase())) { xml.addTag(music, files[a].getName(), "", files[a].getAbsolutePath()); } if (isMediaVideo(files[a].getName().toLowerCase())) { xml.addTag(video, files[a].getName(), "", files[a].getAbsolutePath()); } } } } private void showAll(XML.XMLTag tag) { tree.makeVisible(new TreePath(tag.getPath())); int cnt = tag.getChildCount(); for(int a=0;a<cnt;a++) { showAll(tag.getChildAt(a)); } } private void showAll() { showAll(xml.root); } private void addRow(JLabel icon, boolean select, int track, String name, String artist, String album, String length, String filename) { model.addRow(new Object[] {icon, select, track, name, artist, album, length}); tableFiles.add(filename); } private void clearList() { tableFiles.clear(); while (model.getRowCount() > 0) model.removeRow(0); } private void listTag(XML.XMLTag tag) { clearList(); int cnt = tag.getChildCount(); int track = 1; for(int a=0;a<cnt;a++) { String name = tag.getChildAt(a).name; int idx = name.lastIndexOf("."); if (idx != -1) name = name.substring(0, idx); addRow(null, false, track++, name, "Unknown", "Unknown" ,"?", tag.getChildAt(a).content); } } /** Play a file in current playlist. */ private void play(int idx) { if (playing) {stop(true); return;} model.setValueAt(icon_playing, idx, 0); currentIdx = idx; playing = true; readThread = new ReadThread(tableFiles.get(idx)); readThread.start(); play.setIcon(pauseIcon); timer = new javax.swing.Timer(1000, this); timer.start(); } /** Play a file directly. */ public void play(File file) { if (playing) {stop(true); return;} currentIdx = -1; playing = true; readThread = new ReadThread(file.getAbsolutePath()); readThread.start(); play.setIcon(pauseIcon); timer = new javax.swing.Timer(1000, this); timer.start(); } public synchronized void stop(boolean wait) { if (!playing) return; if (decoder == null) return; if (readThread == null) return; timer.stop(); timer = null; playing = false; if (wait) { JFLog.log("wait for read thread"); try {readThread.join();} catch (Exception e) {JFLog.log(e);} JFLog.log("read thread done"); } play.setIcon(playIcon); if (currentIdx != -1) model.setValueAt(null, currentIdx, 0); time.setValue(0); if (videoPanel != null) videoPanel.time().setValue(0); } private void pause() { if (!playing) return; if (paused) return; if (decoder == null) return; play.setIcon(playIcon); if (videoPanel != null) videoPanel.play().setIcon(playIcon); paused = true; if (currentIdx != -1) model.setValueAt(icon_paused, currentIdx, 0); } private void resume() { if (!paused) return; play.setIcon(pauseIcon); if (videoPanel != null) videoPanel.play().setIcon(pauseIcon); paused = false; if (currentIdx != -1) model.setValueAt(icon_playing, currentIdx, 0); } public void actionPerformed(ActionEvent ae) { //timer task try { if (!playing) return; if (seekPosition != -1) return; //seeking long pos; if (fps > 0) { //video pos = (long)(frameCount / decoder.getFrameRate()); } else { //audio only pos = audioCount / (44100 * chs); } updatingPos = true; if (mediaLength == 0) { time.setValue(0); if (videoPanel != null) videoPanel.time().setValue(0); } else { int ipos = (int)((pos * 100) / mediaLength); // JFLog.log("ipos=" + ipos); time.setValue(ipos); if (videoPanel != null) videoPanel.time().setValue(ipos); } updatingPos = false; } catch (Exception e) { e.printStackTrace(); } } private void listDiscs() { int cnt = media.getChildCount(); for(int a=0;a<cnt;a++) { xml.deleteTag(media.getChildAt(a)); } File file = new File("/media"); if (!file.exists()) return; if (!file.isDirectory()) return; File folders[] = file.listFiles(); if (folders == null) return; cnt = folders.length; int discNo = 1; for(int a=0;a<cnt;a++) { if (folders[a].getName().startsWith("CDROM")) { xml.addTag(media, "Disc " + discNo++, "", folders[a].getName()); } } showAll(); } private String seconds2String(long time) { long hrs = time / 60 / 60; time -= (hrs * 60 * 60); long mins = time / 60; time -= (mins * 60); long secs = time; if (hrs > 0) { return "" + hrs + ":" + mins + ":" + secs; } return "" + mins + ":" + secs; } private void listDisc(XML.XMLTag tag) { clearList(); File file = new File("/media/" + tag.content); File tracks[] = file.listFiles(); int track = 1; String artist = "Unknown"; String album = "Unknown"; GetNamesDialog dialog = new GetNamesDialog(MediaApp.frame, true); dialog.setVisible(true); if (dialog.accepted) { artist = dialog.getArtist(); album = dialog.getAlbum(); } for(int a=0;a<tracks.length;a++) { if (!tracks[a].getName().endsWith(".wav")) continue; long length = tracks[a].length() / 176800; //seconds String lenStr = seconds2String(length); String name = tracks[a].getName(); int idx = name.lastIndexOf("."); if (idx != -1) name = name.substring(0, idx); addRow(null, false, track, name, artist, album, lenStr, tracks[a].getAbsolutePath()); track++; } if (track > 1) extract.setEnabled(true); } private boolean copyFile(File src, File dst) { try { FileInputStream fis = new FileInputStream(src); File dstParent = dst.getParentFile(); dstParent.mkdirs(); FileOutputStream fos = new FileOutputStream(dst); int length = fis.available(); int copied = 0; byte buf[] = new byte[4096]; while (copied < length) { if (abort) break; int read = fis.read(buf); if (read == -1) throw new Exception("file io error"); if (read == 0) continue; fos.write(buf, 0, read); copied += read; } fis.close(); fos.close(); if (abort) { dst.delete(); return false; } return true; } catch (Exception e) { JFLog.log(e); return false; } } private void extract() { if (ripping) {abort = true; return;} ripping = true; extract.setText("Abort"); new Thread() { public void run() { String path = JF.getUserPath() + "/Music/"; File file = new File(path); file.mkdirs(); int cnt = model.getRowCount(); boolean sel[] = new boolean[cnt]; String names[] = new String[cnt]; String files[] = new String[cnt]; String artist[] = new String[cnt]; String album[] = new String[cnt]; for(int a=0;a<cnt;a++) { sel[a] = (Boolean)model.getValueAt(a, 1); names[a] = (String)model.getValueAt(a, 3); artist[a] = (String)model.getValueAt(a, 4); album[a] = (String)model.getValueAt(a, 5); files[a] = (String)tableFiles.get(a); } for(int a=0;a<cnt;a++) { if (abort) break; if (!sel[a]) continue; model.setValueAt(icon_ripping, a, 0); if (!copyFile( new File(files[a]), new File(path + artist[a] + "/" + album[a] + "/" + names[a] + ".wav"))) { JF.showError("Error", "Extract failed for track " + (a+1)); break; } model.setValueAt(icon_ripped, a, 0); } ripping = false; extract.setText("Extract"); } }.start(); } private void next() { if (playing) stop(true); int nextIdx = currentIdx + 1; if (random.isSelected()) { if (randomIdx+1 >= randomIdxList.length) return; randomIdx++; nextIdx = randomIdxList[randomIdx]; } if (nextIdx >= model.getRowCount()) return; play(nextIdx); } private void prev() { if (playing) stop(true); int nextIdx = currentIdx - 1; if (random.isSelected()) { if (randomIdx == 0) return; randomIdx--; nextIdx = randomIdxList[randomIdx]; } if (nextIdx < 0) return; play(nextIdx); } private void addFolder() { JFileChooser chooser = new JFileChooser(); chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); chooser.setMultiSelectionEnabled(false); chooser.setCurrentDirectory(new File(JF.getUserPath())); if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) return; currentIdx = -1; //BUG : status will no longer get updated addLibrary(chooser.getSelectedFile()); } public void updateToolbar() { int idx = table.getSelectedRow(); if (idx == -1) return; if ((idx == currentIdx) && (playing)) play.setIcon(pauseIcon); else play.setIcon(playIcon); } public void log(String msg) { JFLog.log("" + System.currentTimeMillis() + ":" + msg); } private AudioBuffer audio_buffer; private VideoBuffer video_buffer; final int audio_bufsiz = 1024; final int chs = 2; //currently all formats are converted to stereo float fps; int width, height; int new_width, new_height; boolean resizeVideo; Object sizeLock = new Object(); //buffer size in seconds //too small can cause problems //too large causes resizes to take a long time to take effect //the problem is that some video files are not interlaced very well final int buffer_seconds = 4; final int pre_buffer_seconds = 2; public class ReadThread extends Thread implements MediaIO { private String file; int m_in; //input data int m_out[] = new int[4]; //output data RandomAccessFile raf; long fileRead = 0; byte fileBuf[] = new byte[64*1024]; public ReadThread(String file) { this.file = file; } public void run() { frameCount = 0; audioCount = 0; seekPosition = -1; audio_buffer = new AudioBuffer(44100, chs, buffer_seconds); video_buffer = null; width = -1; height = -1; resizeVideo = false; eof = false; preBuffering = true; try { decoder = new MediaDecoder(); if (decoder == null) throw new Exception("Unable to allocate decoder"); raf = new RandomAccessFile(file, "r"); System.out.println("file=" + file); if (!decoder.start(this, -1, -1, chs, 44100, true)) throw new Exception("Unable to start decoder"); fileLength = new File(file).length(); String err = null; mediaLength = decoder.getDuration(); JFLog.log("Duration=" + mediaLength); fps = decoder.getFrameRate(); JFLog.log("FPS=" + fps); if (fps > 0) { videoPanel = new VideoPanel(); java.awt.EventQueue.invokeLater(new Runnable() { public void run() { setPanel(videoPanel); } }); videoPanel.start(); width = getWidth(); height = getHeight(); decoder.resize(width, height); video_buffer = new VideoBuffer(width, height, buffer_seconds * (int)fps); playThread = new PlayAudioVideoThread(); playThread.start(); } else { playThread = new PlayAudioOnlyThread(); playThread.start(); } JFLog.log("Video Bit Rate=" + decoder.getVideoBitRate()); JFLog.log("Audio Bit Rate=" + decoder.getAudioBitRate()); int avBitRate = decoder.getVideoBitRate() + decoder.getAudioBitRate(); if (avBitRate == 0) avBitRate = 64000; if (mediaLength <= 0) { mediaLength = fileLength / (avBitRate / 8); JFLog.log("Calculated Duration=" + mediaLength); } while (playing && !eof) { if (paused) { JF.sleep(100); //TODO:use a lock with wait() and notify() instead preBuffering = true; //pre buffer again after unpause continue; } if ((video_buffer != null && video_buffer.size() >= fps * (buffer_seconds-1)) || (audio_buffer.size() > (44100 * chs * (buffer_seconds-1)))) { preBuffering = false; //in case we don't even have pre_buffer_seconds of video frames int sleep; if (fps > 0) { sleep = 1000 / (int)fps; // JFLog.log("video sleeping:" + sleep + ":" + fps); } else { sleep = 1000 / ((44100 * chs) / (audio_bufsiz)); // JFLog.log("audio sleeping:" + sleep); } JF.sleep(sleep); continue; } if (seekPosition != -1) { long seekTime = (mediaLength / 100) * seekPosition; if (!decoder.seek(seekTime)) { JFLog.log("Seek failed"); } else { synchronized(countLock) { frameCount = (long)(seekTime * decoder.getFrameRate()); audioCount = seekTime * 44100 * chs; } } seekPosition = -1; video_buffer.clear(); audio_buffer.clear(); } if (resizeVideo) { synchronized(sizeLock) { width = new_width; height = new_height; decoder.resize(width, height); resizeVideo = false; } } switch (decoder.read()) { case MediaCoder.AUDIO_FRAME: //audio packet read short audio[] = decoder.getAudio(); audio_buffer.add(audio, 0, audio.length); break; case MediaCoder.VIDEO_FRAME: //video packet read int video[] = decoder.getVideo(); JFImage img = video_buffer.getNewFrame(); if (img != null) { if ((img.getWidth() != width) || (img.getHeight() != height)) { img.setSize(width, height); } img.putPixels(video, 0, 0, width, height, 0); video_buffer.freeNewFrame(); } else { JFLog.log("Warning : VideoBuffer overflow"); } break; case MediaCoder.END_FRAME: eof = true; break; } } if (err != null) JF.showError("Error", err); } catch (Exception e) { JF.showError("Error", e.toString()); JFLog.log(e); } try { JFLog.log("wait for play thread"); playThread.join(); JFLog.log("play thread done"); } catch (Exception e) { JFLog.log(e); } playThread = null; audio_buffer = null; video_buffer = null; if (videoPanel != null) { videoPanel.stop(); videoPanel = null; setPanel(MainPanel.this); } if (playing) MainPanel.this.stop(false); decoder = null; JFLog.log("read thread exit"); } public int read(MediaCoder coder, byte data[]) { int read = 0; try { read = raf.read(data, 0, data.length); } catch (Exception e) { JFLog.log(e); return read; } if (read == -1) read = 0; return read; } public int write(MediaCoder coder, byte data[]) { // jfmedia does not create media files return 0; } public long seek(MediaCoder coder, long pos, int how) { long opos = pos; try { switch (how) { case MediaCoder.SEEK_SET: break; //seek set case MediaCoder.SEEK_CUR: pos += raf.getFilePointer(); break; //seek cur case MediaCoder.SEEK_END: pos += raf.length(); break; //seek end } raf.seek(pos); } catch (Exception e) { JFLog.log(e); } return pos; } } public class PlayAudioVideoThread extends Thread { public void run() { double frameDelay = 1000.0f / fps; double samplesPerFrame = (44100.0 * ((double)chs)) / fps; JFLog.log("samplesPerFrame=" + samplesPerFrame); double samplesToWrite = 0; AudioOutput output = new AudioOutput(); output.start(chs, 44100, 16, audio_bufsiz * 2 /*bytes*/, "<default>"); short samples[] = new short[audio_bufsiz]; double current = System.currentTimeMillis(); int skip = 0; while (playing) { if (preBuffering) { //wait till buffers are 50% full before starting while (!eof && playing && preBuffering) { if (video_buffer.size() >= (fps * pre_buffer_seconds)) break; JF.sleep(25); } preBuffering = false; for(int a=0;a<2;a++) output.write(samples); //prime audio output } samplesToWrite += samplesPerFrame; if (eof) { if ((video_buffer.size() == 0) && (audio_buffer.size() < audio_bufsiz)) break; } while (audio_buffer.size() >= audio_bufsiz && samplesToWrite >= audio_bufsiz) { audio_buffer.get(samples, 0, audio_bufsiz); output.write(samples); samplesToWrite -= audio_bufsiz; synchronized(countLock) { audioCount += audio_bufsiz; }; } if (video_buffer.size() > 0) { JFImage img = video_buffer.getNextFrame(); synchronized(countLock) { frameCount++; } while (skip > 0 && video_buffer.size() > 1) { if (img == null) break; skip--; video_buffer.freeNextFrame(); img = video_buffer.getNextFrame(); synchronized(countLock) { frameCount++; } } skip = 0; if (img != null) { videoPanel.setImage(img); video_buffer.freeNextFrame(); } } else { JFLog.log("Playback too slow - skipping a frame"); skip++; } current += frameDelay; double now = System.currentTimeMillis(); double delay = (current - now); if (delay >= 1.0) { JF.sleep((int)delay); } } output.stop(); JFLog.log("play thread exit"); } } public class PlayAudioOnlyThread extends Thread { public void run() { double frameDelay = 1000.0 / ((44100.0 * chs) / (audio_bufsiz)); double samplesPerFrame = audio_bufsiz; double samplesToWrite = 0; AudioOutput output = new AudioOutput(); output.start(chs, 44100, 16, audio_bufsiz * 2 /*bytes*/, "<default>"); short samples[] = new short[audio_bufsiz]; double current = System.currentTimeMillis(); while (playing) { if (preBuffering) { //wait till buffers are 50% full before starting while (!eof && playing && preBuffering) { if (audio_buffer.size() >= (44100 * chs * pre_buffer_seconds)) break; JF.sleep(25); } preBuffering = false; for(int a=0;a<2;a++) output.write(samples); //prime output } samplesToWrite += samplesPerFrame; if (eof) { if (audio_buffer.size() < audio_bufsiz) break; } while (audio_buffer.size() >= audio_bufsiz && samplesToWrite >= audio_bufsiz) { audio_buffer.get(samples, 0, audio_bufsiz); output.write(samples); samplesToWrite -= audio_bufsiz; synchronized(countLock) { audioCount += audio_bufsiz; }; } current += frameDelay; double now = System.currentTimeMillis(); double delay = (current - now); if (delay >= 1.0) { JF.sleep((int)delay); } } output.stop(); JFLog.log("play thread exit"); } } public void setVideoSize(int width,int height) { synchronized(sizeLock) { new_width = width; new_height = height; resizeVideo = true; } } public void setPanel(JPanel panel) { JFLog.log("setPanel:" + panel); MediaApp.frame.setContentPane(panel); panel.revalidate(); } public void playOrPause() { if (paused) resume(); else pause(); } public void seek() { if (!playing) return; if (updatingPos) return; if (videoPanel != null) seekPosition = videoPanel.time().getValue(); else seekPosition = time.getValue(); // JFLog.log("seek=" + seekPosition); } }