¡Hola a todos! Como dije en mi primer post , no soy programador, sino aficionado. Traté de escribir mis manualidades en diferentes idiomas, pero comencé con Java. Sobre todo de la familia Java, me gustó la plataforma JavaFX. Más precisamente, un montón de JavaFX + FXML, donde escribimos la lógica en el controlador y describimos la interfaz gráfica en un archivo fxml separado. La radio se acaba de escribir con este paquete.
La biblioteca JLayer se utiliza para la reproducción. La clase incorporada MediaPlayer por alguna razón se negó a trabajar para mí. La grabación y reproducción se realizan en secuencias separadas. Por el bien del experimento, intenté iniciar la reproducción en el hilo principal de la aplicación. Tengo una interfaz muerta. Obtuve lo mismo cuando intenté escribir en el hilo principal.
GitHub. NetBeans 8.2 Scene Builder Gluon. , , , , .
:
«Station» , . «Record» , . «Reference» « », .
. . - , , .
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="300.0" prefWidth="535.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="radioplayer.PlayerController">
<ListView fx:id="stationsListView" focusTraversable="false" layoutX="14.0" layoutY="36.0" prefHeight="246.0" prefWidth="200.0" AnchorPane.bottomAnchor="14.0" AnchorPane.leftAnchor="14.0" AnchorPane.topAnchor="36.0" />
<Button fx:id="playButton" focusTraversable="false" layoutX="240.0" layoutY="177.0" mnemonicParsing="false" onAction="#playAction" prefHeight="103.0" prefWidth="130.0" text="PLAY" AnchorPane.bottomAnchor="14.0" AnchorPane.rightAnchor="165.0">
<font>
<Font name="System Bold" size="22.0" />
</font></Button>
<Button fx:id="stopButton" focusTraversable="false" layoutX="391.0" layoutY="177.0" mnemonicParsing="false" onAction="#stopAction" prefHeight="103.0" prefWidth="130.0" text="STOP" AnchorPane.bottomAnchor="14.0" AnchorPane.rightAnchor="14.0">
<font>
<Font name="System Bold" size="22.0" />
</font></Button>
<Label fx:id="nameStation" layoutX="240.0" layoutY="46.0" prefHeight="113.0" prefWidth="279.0" wrapText="true" AnchorPane.bottomAnchor="141.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="36.0">
<font>
<Font name="System Bold Italic" size="24.0" />
</font></Label>
<MenuBar prefHeight="29.0" prefWidth="535.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<menus>
<Menu mnemonicParsing="false" text="Station">
<items>
<MenuItem mnemonicParsing="false" onAction="#addAction" text="Add" />
<MenuItem mnemonicParsing="false" onAction="#editAction" text="Edit" />
<MenuItem mnemonicParsing="false" onAction="#deleteAction" text="Delete" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Record">
<items>
<MenuItem fx:id="recordItem" mnemonicParsing="false" onAction="#recordAction" text="To begin" />
<MenuItem fx:id="stopRecordItem" mnemonicParsing="false" onAction="#stopRecordAction" text="Stop" />
<MenuItem mnemonicParsing="false" onAction="#directoryRecordAction" text="Records Directory" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Reference">
<items>
<MenuItem mnemonicParsing="false" onAction="#appInfoAction" text="About the program" />
<MenuItem mnemonicParsing="false" onAction="#exitAction" text="Exit" />
</items>
</Menu>
</menus>
</MenuBar>
</AnchorPane>
(toast . ):
.root{
-fx-background-color: grey;
}
.button{
-fx-background-radius: 40;
-fx-border-radius: 40;
-fx-text-fill: white;
}
.button:hover{
-fx-background-color: derive(-fx-base, 18%);
-fx-border-style: solid;
-fx-border-width: 1;
-fx-border-color: derive(-fx-base, -15%);
-fx-cursor: hand;
}
.button:pressed{
-fx-text-fill: black;
}
.list-view, .list-view .viewport, .list-view .content{
-fx-background-color: gainsboro;
}
.list-view:hover{
-fx-cursor: hand;
}
.toast{
-fx-background-radius: 30;
-fx-border-radius: 30;
-fx-background-color: black;
-fx-padding: 20;
}
#nameStation{
-fx-text-fill: white;
}
#playButton{
-fx-background-color: blue;
}
#stopButton{
-fx-background-color: red;
}
:
taskPlayer = new Task() {
@Override
public Void call() {
try {
radioUrl = new URL(urlString);
InputStream in = radioUrl.openStream();
InputStream is = new BufferedInputStream(in);
player = new Player(is);
player.play();
} catch (FileNotFoundException e) {
e.getMessage();
} catch (IOException | JavaLayerException e) {
e.getMessage();
}
return null;
}
};
new Thread(taskPlayer).start();
, . , JLayer. :
taskRecord=new Task() {
@Override
public Void call() throws FileNotFoundException, IOException{
output = new FileOutputStream(reader(file.getAbsolutePath())+
separator+nameStation.getText()+"-"+new Date().toString().replace(":","-")+".mp3");
InputStream in = radioUrl.openStream();
InputStream is = new BufferedInputStream(in);
byte data[] = new byte[1024];
int count;
while ((count = is.read(data)) != -1) {
output.write(data, 0, count);
}
output.flush();
return null;
}
};
new Thread(taskRecord).start();
, , URL. , :
private void createDefaultStations(){
String[] stationNames = {"NonStopPlay","Classical Music","Fip Radio","Jazz Legends","Joy Radio","Live-icy","Music Radio","Radio Electron","Dubstep","Trancemission"};
String[] stationUrls = {"http://stream.nonstopplay.co.uk/nsp-128k-mp3","http://stream.srg-ssr.ch/m/rsc_de/mp3_128","http://direct.fipradio.fr/live/fip-midfi.mp3","http://jazz128legends.streamr.ru/","http://airtime.joyradio.cc:8000/airtime_192.mp3","http://live-icy.gss.dr.dk:8000/A/A05H.mp3","http://ice-the.musicradio.com/CapitalXTRANationalMP3","http://radio-electron.ru:8000/128","http://air.radiorecord.ru:8102/dub_320","http://air.radiorecord.ru:8102/tm_320"};
for(int i=0;i<10;i++){
writer(path+separator+stationNames[i], stationUrls[i]);
}
}
dirCreator, RadioStations, . :
private void dirCreator(final String fPath) {
final File file = new File(fPath);
if (!file.exists()) {
file.mkdir();
if(file.exists()){
alertWindow("The <RadioStations> directory has been created.\nYour radio stations will be here:\n"+fPath);
createDefaultStations();
}else{
alertWindow("Error!\nThe <RadioStations> directory will not be created.\n" +
"Try creating the specified directory manually in the following path:\n"+fPath+"\nThe program will be closed.");
System.exit(0);
}
}
}
. , :
private boolean permissionRead(File file){
if(!file.canRead()){
file.setReadable(true);
return !file.canRead();
}
return false;
}
private boolean permissionWrite(File file){
if(!file.canWrite()){
file.setWritable(true);
return !file.canWrite();
}
return false;
}
RadioStations:
@Override
public void initialize(URL url, ResourceBundle rb) {
parentPath = System.getProperty("user.home");
path=parentPath+separator+"RadioStations";
this.dirCreator(this.path);
File f=new File(path);
if(permissionRead(f)||permissionWrite(f)){
if(permissionRead(f)&&permissionWrite(f)){
alertWindow("Failed to get permission to read and write files to the <RadioStations> directory.\nTry to give permission manually.");
}else if(permissionRead(f)){
alertWindow("Failed to get permission to read files in directory <RadioStations>.\nTry to give permission manually.");
}else{
alertWindow("Failed to get permission to write files to <RadioStations> directory.\nTry to give permission manually.");
}
System.exit(0);
}
showStationsList();
stopButton.setDisable(true);
recordItem.setDisable(true);
stopRecordItem.setDisable(true);
}
, . , , . :
final Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setResizable(true);
alert.getDialogPane().setPrefSize(500,200);
alert.setTitle("Saving Recordings");
alert.setHeaderText("");
alert.setContentText("The default path for your recordings is:\n"+f.getAbsolutePath()+"\nChange?");
ButtonType buttonTypeEdit = new ButtonType("Edit", ButtonBar.ButtonData.OK_DONE);
ButtonType buttonTypeDefault = new ButtonType("Default", ButtonBar.ButtonData.FINISH);
ButtonType buttonTypeCancel = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
alert.getButtonTypes().setAll(buttonTypeEdit, buttonTypeDefault, buttonTypeCancel);
final Optional<ButtonType> resultAlert = alert.showAndWait();
:
, . «Edit», , «Default», . «Cancel» .
. :
Dialog dialog = new Dialog<>();
dialog.setTitle("Station Creation");
dialog.setHeaderText("Enter the name and url of the radio station");
ButtonType createButtonType = new ButtonType("Create", ButtonBar.ButtonData.OK_DONE);
ButtonType cancelButtonType = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
dialog.getDialogPane().getButtonTypes().addAll(createButtonType,cancelButtonType);
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(20, 150, 10, 10));
TextField stationName = new TextField();
TextField url = new TextField();
grid.add(new Label("Title:"), 0, 0);
grid.add(stationName, 1, 0);
grid.add(new Label("Url:"), 0, 1);
grid.add(url, 1, 1);
dialog.getDialogPane().setContent(grid);
Optional<ButtonType> result = dialog.showAndWait();
. . :
, .
. . .
package radioplayer;
import javafx.application.Application;
import java.awt.*;
import javafx.stage.Stage;
/**
*
* @author alex
*/
public class Splash extends Application{
public static void main(final String[] args) {
SplashScreen splash = SplashScreen.getSplashScreen();
try {
Thread.sleep(3000L);
}
catch (InterruptedException ex) {
ex.getMessage();
}
if (splash != null) {
splash.close();
Application.launch(RadioPlayer.class, args);
}
}
@Override
public void start(Stage primaryStage) throws Exception {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}
:
:
-splash:src/images/splash.png
:
SplashScreen-Image: images/splash.png
, Android
, Android OS. :
:
package radioplayer;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
/**
*
* @author alex
*/
public class Toast {
void setMessage(final String toastMsg){
Stage toastStage=new Stage();
toastStage.setResizable(false);
toastStage.initStyle(StageStyle.TRANSPARENT);
Text t = new Text(toastMsg);
t.setFont(Font.font("Verdana",20));
t.setFill(Color.WHITE);
StackPane root = new StackPane(t);
root.getStyleClass().add("toast");
root.setOpacity(0);
Scene scene = new Scene(root);
scene.getStylesheets().add((getClass().getResource("style.css")).toExternalForm());
scene.setFill(null);
toastStage.setScene(scene);
toastStage.show();
Timeline tl1 = new Timeline();
KeyFrame fadeInKey1 = new KeyFrame(Duration.millis(500),new KeyValue (toastStage.getScene().getRoot().opacityProperty(), 1));
tl1.getKeyFrames().add(fadeInKey1);
tl1.setOnFinished((ae) ->
new Thread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.getMessage();
}
Timeline tl2 = new Timeline();
KeyFrame fadeOutKey1 = new KeyFrame(Duration.millis(500), new KeyValue(toastStage.getScene().getRoot().opacityProperty(), 0));
tl2.getKeyFrames().add(fadeOutKey1);
tl2.setOnFinished((aeb) -> toastStage.close());
tl2.play();
}).start());
tl1.play();
}
}
, NetBeans , , JLayer. . , .
JLayer , build.xml :
<target name="package-for-store" depends="jar">
<property name="store.jar.name" value="Radio"/>
<property name="store.dir" value="store"/>
<property name="store.jar" value="${store.dir}/${store.jar.name}.jar"/>
<echo message="Packaging ${application.title} into a single JAR at ${store.jar}"/>
<delete dir="${store.dir}"/>
<mkdir dir="${store.dir}"/>
<jar destfile="${store.dir}/temp_final.jar" filesetmanifest="skip">
<zipgroupfileset dir="dist" includes="*.jar"/>
<zipgroupfileset dir="dist/lib" includes="*.jar"/>
<manifest>
<attribute name="Main-Class" value="radioplayer.Splash"/>
<attribute name="SplashScreen-Image" value="images/splash.png"/>
</manifest>
</jar>
<zip destfile="${store.jar}">
<zipfileset src="${store.dir}/temp_final.jar"
excludes="META-INF/*.SF, META-INF/*.DSA, META-INF/*.RSA"/>
</zip>
<delete file="${store.dir}/temp_final.jar"/>
</target>
« », «package-for-store». «store» .
SourceForge. !