Sunday, January 20, 2013

Why doesn't Joobik Player show all my iTunes videos?

Apple do sell some of their videos with Digital Rights Management (DRM), some without. This policy differs from country to country. E.g. in Germany they usually sell music videos without DRM, but movies and tv shows are DRM protected. Depending on where you live some of your purchased videos might be DRM protected. You can check this in iTunes by inspecting the "Kind" property on a video. If it reads something like "Protected video file", Joobik Player will not be able to play it.



Please check all of the videos not appearing in Joobik Player for the "Kind" property in iTunes. If all of them are protected, Joobik Player hides them. This is a restriction by Apple. They let only their own apps access DRM protected content.

If you find any unprotected video not appearing in Joobik Player, this blog post might help.

Monday, January 7, 2013

Sunday, January 6, 2013

Could we get play count syncing with iTunes?

This article has moved to the new Joobik Player website: http://www.joobikplayer.com/f-a-q

What does Joobik mean?

Joobik is a phonetic mixture of "Jukebox" and "ubiquitous". So it means: Your everywhere-jukebox! I thought, it looks a bit better written with "Joo", instead of "Ju".

Thursday, December 13, 2012

Joobik Video Playlist Player- iTunes App Store Reviews Replied

Here I will answer some of the reviews of Joobik Video Playlist Player I received from iTunes app stores all over the world. Unfortunately the iTunes store doesn't give me the possibility to answer a review directly in the store. So this post is my way of giving feedback.

First of all, I want to say thank you to anybody who leaves a review comment to one of my apps at the iTunes store - Be it positive or negative! All these comments are valuable input and inspiration to my further app development!

But now let's start. I'll post the original comments, english translations (if needed), plus my replies, all sorted by date.


2012/12/31 Endlich kontinuierlich Videos im Auto abspielen!


Germany - McGybrush
Joobik Player 2.0

Review (English Translation) - Finally I can play videos continuously in the car
There are some players that play videos continuously. But this one uses my existing smart playlists. I don't have to create new playlists or edit existing ones when I load new videos into my smart lists.
To my opinion, the swipe-to-skip-gesture is inverted. I would like to have this changed.

Review (Original - German)
Es gibt einige Player die das gleiche können. Aber dieser nutzt meine vorhandenen Intelligenten Wiedergabelisten und muss wenn ich öfter Videos nachlade nie eine eigene neue Wiedergabeliste erstellen bzw die alten erweitern. Ich kann einfach die nutzen die sowieso schon da sind. Das Titel weiter swipen mit der Gestensteuerung empfinde ich für mich leider als Spiegelverkehrt. In der CarTunes APP die so ähnlich für NICHT Videos ist, fühlt sich das swipen richtig rum an. Die dort enthaltenen Gesten sollten auch hier noch eingeführt werden. Trotzdem 5 Sterne.

Reply
Thanks a lot for this feature request, McGybrush! In Joobik Player 2.1 the swipe-to-skip gesture will be configurable in the app settings.


2012/12/26 I can use my playlists again on my Ipad!


United States - Wendy NJ
Joobik Player 2.0

Review
Thank you so much for making this app.  Sadly my video playlists stopped working on my iphone and ipad 10/2012 and I figured out a workaround by changing the media type to music video.  It fixed the iphone problem, but the playlists still would not work on my ipad 1 and after some research, I found this app.  I liked that this app took my existing playlists and listed them as opposed to another where I had to recreate the playlists.  I do wish that it would show folders within folders, like my playlists in itunes, instead of listing all the folders separately by name.  Maybe a future version could support that.  Also, it is a little slow getting to the videos, but that could be because I have an original ipad 1!  I am very happy with this app as it allows me to see my videos easily in an organized fashion!  THANK YOU!

Reply
I'm very unhappy with the folder-thing, too, because this is how I do organize my playlists as well. So if there where a way of telling which playlist is actually a folder, it'd be the first thing I'd build into Joobik Player. But currently the guys from Apple do not provide any means to third party developers which would let them identify a folder hierarchy. A playlist folder on the iPad looks like a smart playlist to me, which contains only tracks, no other playlists.


2012/12/14 Super App!


Germany - Oneo88
Joobik Player 2.0

Review (English Translation)
I own the App for 3 months and am very happy. Only shortcoming is that after shuffling it never starts playback from position 1, but from somewhere in the middle of the list.

Review (Original - German)
Besitze die APP. Seit 3 Monaten und bin sehr zufrieden. Einzigstes Manko ist das man bei der Shuffle Wiedergabe nie von Position 1 anfängt sondern irgendwo in der liste anfängt .

Reply
Thanks a lot for this bug report, Oneo88! In Joobik Player 2.1 it will be fixed!




2012/12/11 Playlists keep vanishing


United States - Apple IIc User
Joobik Player 1.6

Review
This is a good program … until all my playlists vanish, first from my iPad Mini, and then upon syncing, from iTunes. The actual videos remain intact on the iPad, but the lists are gone. This has happened a few times.

Reply
In this case I can take all the fear from all of you: For a third-party-app (like Joobik Player is) it isn't possible to do any modifications to your iPad's, or (even worse) your Mac's or PC's iTunes library. Apple's programming interfaces do prevent this reliably. If you doubt, please inquire the Apple support team!So I guess your problems come either from the new behaviour and UI of iTunes 11, or you simply didn't sync your playlists correctly. Please find some playlist syncing instructions here.


2012/12/02 Movie output

United States - Sancho2012
Joobik Player 1.6

Review
Is there a way that you can make it so that videos can play back when its hooked up with a j-link cable or do it through bluetooth?

Reply
If by "j-link" you mean J-Link, then it isn't. Just like it isn't for bluetooth. The current Apple programming interface available to third party developers does only support external screens via AirPlay out of the box. Apple does actively prevent third party apps from outputting video via cable. I guess the intention is to push AirPlay. Even if I could find a way of enabling tv out it could be they would reject my app from the app store due to their API usage rules.
There seem to be some workarounds for custom videos which are copied to the iPad via different ways than iTunes (like web videos). But I couldn't get this to work for videos from the iTunes library.



2012/11/17 Fantastic App

United Kingdom - Kaves17
Joobik Player 1.6

Review
Great app, allows me to play all my video playlists. Could we get play count syncing to the music/video app?

Reply
Unfortunately Apple does not give write access to iTunes meta data to 3rd party apps. So it's currently not possible to modify play count and sync it back to any Apple app or iTunes.

Monday, April 9, 2012

How to sync video playlists from iTunes to iPod, iPhone, or iPad


Joobik Player plays videos from playlists synced to your iPod, iPhone, or iPad. To use it, please create a video playlist in iTunes and then sync the list to your device, not only the videos themselves.


For example: To sync a movie playlist, on the tab where you select videos for synchronization in iTunes, you have to either uncheck the "Automatically include" checkbox, or with the checkbox checked you must choose any other option than "All movies" from the drop down list in order to display the playlists for syncing.






On the "TV-Shows" tab it is the same.

If you have a mixed playlists, containing movies and tv shows for example, this playlist must be selected under the "Movies" and the "TV Shows" tab for syncing. Otherwise it will only contain either of the video types when synced to your iOS device.

iTunes Match Users: If you are using iTunes Match to sync your videos to your iOS device, you must download the videos from iCloud to the device in the first place. Only then they will appear in Joobik Player. Downloading can be done with the standard Apple iOS Video or Music App.
When iTunes Match is turned on on your iOS device, Joobik Player will not be able to recognize iTunes Folders.

Detailed instructions about iTunes Match can be found in this blog post.



Wednesday, December 8, 2010

Mapping Dates and Time Zones with Hibernate, Part 2: Few Solutions

Go back to Part 1 of this article

In the first part of this article I was talking about the problems when mapping date/time to a database table using Hibernate. In part two I will talk about the solutions.

The quick Way: Use Property Access Type and implement the Setter

Tell Hibernate to use setter and getter methods for field access, instead of using reflection to modify the entity object's fields directly. To do so, you have to put the Hibernate annotations above the getter methods, instead of the class attributes. You MUST move the annotation of the @Id field to the getter to enable property access. But you should do it with all field annotations, for better clarity. You can find a more detailed discussion of Hibernate property access here and here.
Once you did so, you can implement a setter method for the calendar field, which takes the calendar object provided by Hibernate, and creates a new object with the right time zone and date information from it:

package entity;

...

public class GMTDateEntity implements Serializable {

   
    ...

    private Integer pk;

    private Calendar calendar;

    @Id
    @Column(name = "pk", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Integer getPk() {
        return pk;
    }



    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "calendartime")
    public Calendar getCalendar() {
         return calendar;
    }

    public void setCalendar(Calendar calendar) {
         //create new calendar in GMT time zone
        this.calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
       
        //set calendar fields
        this.calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR));
        this.calendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH));
        this.calendar.set(Calendar.DATE, calendar.get(Calendar.DATE));
        this.calendar.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY));
        this.calendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE));
        this.calendar.set(Calendar.SECOND, calendar.get(Calendar.SECOND));
        this.calendar.set(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND));
       
        //recalculate calendar time millis
        this.calendar.getTime();
     }


     ...
}

Because the time zone information is the only thing that is wrong in Hibernate's calendar object, we simply create a new calendar object in the correct time zone, and then copy all required fields from Hibernate's object to our new object. The final call to getTime() will recalculate the calendars internal time milliseconds based on the field and time zone information. Unfortunately, the actual recalculation-methods are protected in java.util.Calendar for reasons I just don't know. So we have to use this less elegant workaround.

The elegant Way: Create a custom Hibernate User Type 

If for any reason you cannot use Hibernate property access, you will have to stick to the advanced art of programming and create a custom user type, which does the mapping from DB result set to Java object for you. Explanation of user type implementation would be beyond topic of this article. But you can find a very good introduction here. Just be aware, that you have to create a mutable user type, as java.util.Calendars are mutable objects.
To put everything short, here is the user type implementation that will serve our purpose:
package usertypes;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;

public class GmtCalendarUserType implements UserType, Serializable {

    private static final long serialVersionUID = 1L;
  
    //The interesting methods:

    @Override
    public void nullSafeSet(PreparedStatement statement, Object value, int index)
            throws HibernateException, SQLException {


        // we have to be null-safe
        if (value == null) {
          Hibernate.CALENDAR.nullSafeSet(statement, null, index); 
          return;
        } 
 
        // we have a Calendar here
        Calendar cal = (Calendar) value;
        // cut millis, as SQL Server uses only 1/300 precision
        long millis = cal.getTimeInMillis();
        millis = millis - (millis % 1000);
        cal.setTimeInMillis(millis);
        // simply delegate to hibernate's built in method
        Hibernate.CALENDAR.nullSafeSet(statement, cal, index);
    }

    @Override
    public Object nullSafeGet(ResultSet resultSet, String[] columnNames,
            Object owner) throws HibernateException, SQLException {

        // we cannot do it like this, because it would initialize the calendar
        // in the jvm's default timezone:
        // Calendar cal = (Calendar) Hibernate.CALENDAR.nullSafeGet(resultSet,
        // columnNames);
        // return cal;

        // We have to create the Calendar object from the DB date string
        String timeString = (String) Hibernate.STRING.nullSafeGet(resultSet,
                columnNames);
        if (timeString == null)
            return null;

        try {
            Date date = this.parseDbDateString(timeString);
            // Init calendar in GMT
            Calendar retValue = new GregorianCalendar(
                    TimeZone.getTimeZone("GMT"));
            retValue.setTime(date);
            // calculate calendar fields
            retValue.getTime();
            return retValue;
        } catch (ParseException e) {
            throw new HibernateException("Could not parse datestring from DB.",
                    e);
        }
    }

    private Date parseDbDateString(String dateString) throws ParseException {
        // create gmt time zone
        TimeZone gmtZone = TimeZone.getTimeZone("GMT");
        // create db date format (cut millis)
        String pattern = "yyyy-MM-dd HH:mm:ss";
        SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
        dateFormat.setTimeZone(gmtZone);
        return dateFormat.parse(dateString);
    }

    //The other UserType methods:
  
    @Override
    public Object assemble(Serializable cached, Object owner)
            throws HibernateException {
        return this.deepCopy(cached);
    }

    @Override
    public Object deepCopy(Object object) throws HibernateException {
        if (object == null)
            return null;
        // we have a calendar here
        Calendar cal = (Calendar) object;
        return cal.clone();
    }

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) this.deepCopy(value);
    }

    @Override
    public boolean equals(Object object1, Object object2)
            throws HibernateException {
        if (object1 == object2) {
            return true;
        }
        if ((object1 == null) || (object2 == null))
            return false;
        return object1.equals(object2);
    }

    @Override
    public int hashCode(Object value) throws HibernateException {
        return value.hashCode();
    }

    @Override
    public boolean isMutable() {
        // Calendar is mutable
        return true;
    }

    @Override
    public Object replace(Object original, Object target, Object owner)
            throws HibernateException {
        return this.deepCopy(original);
    }

    @Override
    public Class returnedClass() {
        return Calendar.class;
    }

    @Override
    public int[] sqlTypes() {
        return new int[] { Types.TIMESTAMP };
    }
}



The nullSafeSet method will inject our Java object into the SQL statement. As Hibernate handles Calendars correctly when persisting, you simply delegate this task to Hibernate here. But since I'm using MS SQL Server in this example, I have to deal with the different precisions of Java dates (1 millisecond prec.) and SQL Server datetime types (1/300 second prec.). I choose the simple way here and simply cut the millis.

The nullSafeGet method does a little more of tweaking. This is where the result set is mapped to our Java Calendar object. If we would use Hibernate's built in mapping here (as in the commented block in this method), we would get the Calendar with the JVM time zone set. So we have to treat the datetime from the SQL result set as a string, and parse the date in GMT from it and create a new GMT GregorianCalendar.

Once we've finished our UsertType, all we have to do is to annotate our GMTDateEntity object to use it:
package entity;

...


@Entity
@Table(name = "datetestgmt")

//Declare the UserType on the class
@TypeDef(name = "gmtCalendar", typeClass = GmtCalendarUserType.class)
public class GMTDateEntity implements Serializable {



...




    
    //Use the UserType on the attribute
    @Type(type = "gmtCalendar")
    @Column(name = "calendartime")
    private Calendar calendar;
 



...

}

Go back to Part 1 of this article